From e6caddc1989bd5433d901c0f0b3bf5629ba316ff Mon Sep 17 00:00:00 2001 From: Antonio <34042064+Desvelao@users.noreply.github.com> Date: Thu, 20 Jul 2023 16:48:16 +0200 Subject: [PATCH] Update release utilities (#5677) * feat: update release utilities to current process - Add new bump script - Port tag.py to NodeJS and allow receive parameters from stdin - Add RELEASING.md file with information about the release process related to the usage of the included scripts - Add release:bump and release:tag package scripts to run these process * remove: remove scripts/tag.py and reference in the Makefile * fix: fix help text in bump and tag scripts * remove: remove stage and commit properties from the package.json * remove: test related to stage property in the package.json * fix: check if there are changes to commit in the tag script - Code formatting - Fix variable name --- Makefile | 4 - RELEASING.md | 243 +++++++++ package.json | 6 +- public/package.test.ts | 19 - scripts/release/bump.js | 304 +++++++++++ scripts/release/lib/logger.js | 20 + scripts/release/lib/read-manifest-package.js | 20 + .../release/lib/update-manifest-package.js | 36 ++ scripts/release/lib/update-manifest-plugin.js | 35 ++ scripts/release/tag.js | 482 ++++++++++++++++++ scripts/tag.py | 151 ------ 11 files changed, 1143 insertions(+), 177 deletions(-) create mode 100644 RELEASING.md create mode 100644 scripts/release/bump.js create mode 100644 scripts/release/lib/logger.js create mode 100644 scripts/release/lib/read-manifest-package.js create mode 100644 scripts/release/lib/update-manifest-package.js create mode 100644 scripts/release/lib/update-manifest-plugin.js create mode 100644 scripts/release/tag.js delete mode 100644 scripts/tag.py diff --git a/Makefile b/Makefile index 24d9ab4370..ad6ba2f360 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,3 @@ prebuild: @echo "- Updating project's versions ..." @node scripts/generate-build-version -tags: - @echo "- Generating Git tags ..." - @python3 scripts/tag.py - diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000000..b6e18cab94 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,243 @@ +## Releasing + +## Runbook + +### Overview + +### Release Phase 1 - Preparation + +#### Files + +The following files must be updated: + +- `package.json`: Defines the package manifest. It contains the following properties: + - `version`: Plugin version. Schema: `{major}.{minor}.{patch}`. Example: 4.4.5 + - `revision`: Plugin revision. Schema: number with 2 digits. This value is reset for each version to `01` and increament for following revisions. + - `pluginPlatform.version`: version of the plugin platform. +- `opensearch_dashboards.json` or `kibana.json`: Defines the plugin manifest. It contains the following properties: + - `version`: Combination of version and revision of the plugin: `{version}-{revision}`. +- `README.md`: References to the version of server, indexer and dashboard applications. +- `CHANGELOG.md`: Changelog of the new release. +- `common/api-info/endpoints.json`: Data related to endpoints and extracted from server's API +- `common/api-info/security-actions.json`: Data related to security actions of extracted from server's API +- Unit tests + +To bump the version, see [# Bump](#Bump) + +#### Create tags + +After the base branches have set the expected [# Files](#files), we must create the tags. + +The tag name follows the pattern: +- final release tag: `v{version}-{platform version}`. Example: `v4.4.5-2.6.0`. +- non-final release tag: `v{version}-{platform version}{suffix}`. Example: `v4.4.5-2.6.0-pre-alpha1`, `v4.4.5-2.6.0-alpha1`, `v4.4.5-2.6.0-rc1`. + +> See the [script instructions](#create-tags---script) that reduces this job. + +#### Create tags - Manually + +Steps: + +1. Switch and update the base branch + +``` +git checkout +git pull +``` + +2. Review if the version, revision and platform values are defined to the target release in the [#Files](#files), if not accomodate them (creating a new commit). + +3. Create the tag + +``` +git tag {tag} -a -m "Wazuh {version} for {platform} {platform version}" +``` + +> replace the placeholders: +> +> - `{tag}`: tag name. Use this schema: `v{version}-{platform version}`. We add suffixes for release candidates or alpha versions: +> - pre-alpha: `-pre-alpha{number}`. Example: `-pre-alpha1`. +> - release candidates: `-rc{number}`. Example: `-rc1`. +> - `{version}`: plugin version +> - `{platform}`: platform name. One of `OpenSearch` or `Kibana` +> - `{platform}`: platform version. + +4. Push the tag + +``` +git push origin {tag} +``` + +> replace the placeholder: + +- `{tag}`: tag name + +#### Create tags - Script + +The process to create all the required tags can be run through a script ( `scripts/release/tag` ). + +For each supported version defined in `scripts/release/tag` + +- edit `revision` package manifest file: `package.json` +- edit the `version` property in plugin manifest file: `opensearch_dashboards.json` or `kibana.json` +- commit +- create tag +- push tag + +The script can be run through the package script `yarn release:tag` too. This is the prefered method because defines some required parameters. + +Steps: + +1. Ensure the target versions are defined as the supported versions in `scripts/release/tag` and the others files are updated. + Currently there are 3 platforms: OpenDistro (Kibana 7.10.2), Kibana 7.16-7.17 and OpenSearch (Wazuh stack). + +2. Bump version/revision/platform version and create the local and remote tag using the package script + +```console +yarn release:tag --revision +``` + +> If the version or the revision is not specified, then it will use the current values from the package manifest file (package.json). +> You can bump the `version` or `platform-version` too or combine them. +> :warning: if the `version` is set, the base branches must exist in the remote repository. + +```console +yarn release:tag --version +yarn release:tag --revision +yarn release:tag --platform-version +yarn release:tag --version --revision --platform-version +``` + +Examples: + +- Change the plugin version + +``` +yarn release:tag --version 4.5.0 +``` +- Change the plugin revision + +``` +yarn release:tag --revision 02 +``` +- Change the platform version + +``` +yarn release:tag --platform-version 2.8.0 +``` +- Change the plugin version, revision and platform version + +``` +yarn release:tag --version 4.5.0 --revision 02 --platform-version 2.8.0 +``` +For tags that needs a suffix, use the `--tag-suffix ` flag. + +``` +yarn release:tag --tag-suffix +``` + +Example: + +``` +yarn release:tag --tag-suffix -rc2 --revision 02 +``` + +If you want to get a report of the tags generated and stored locally, use the `--export-tags `. + +``` +yarn release:tag --revision --export-tags +``` + +Example: + +``` +yarn release:tag --version 4.5.0 --export-tags tags.log +``` + +3. Review the new tags were pushed to the remote repository. + +### Build packages + +## Release Phase 2 - Release testing + +### Release Phase 3 - Release Announcement + +### Release Phase 4 - Post-Release + +### Bump + +It means to increment the version number to a new, unique value. + +Bumping the version requires to do some changes in the source code of the application. See [# Files](#files). + +We have a script (`scripts/release/bump`) to update some of these files: + +- package.json +- opensearch_dashboards.json or kibana.json + +This can be run through the `yarn release:bump` package script too. This is the prefered method because defines some required parameters. **The rest of the files should be changed manually.** + +> The package script sets some required parameters related to manifest files. + +Steps: + +1. Switch to new branch from the base branch to bump + +```console +git checkout +git pull +git checkout -b +``` + +2. Bump the version/revision/platform version using the package script + +```console +yarn release:bump --version +``` + +> You can bump the `revision` or `platform-version` too or combine them. + +```console +yarn release:bump --version +yarn release:bump --revision +yarn release:bump --platform-version +yarn release:bump --version --revision --platform-version +``` + +Examples: + +- Change the plugin version + +``` +yarn release:bump --version 4.5.0 +``` + +- Change the plugin revision + +``` +yarn release:bump --revision 02 +``` + +- Change the platform version + +``` +yarn release:bump --platform-version 2.8.0 +``` + +- Change the plugin version, revision and platform version + +``` +yarn release:bump --version 4.5.0 --revision 02 --platform-version 2.8.0 +``` + +3. Apply manually the changes to the rest of files if needed it. See [# Files](#Files). + +4. Optional. Commit and push the new branch to the remote repository. + +``` +git add . +git commit -m "bump: Bump version/revision/platform version to " +git push origin +``` + +A new branch will be created in the remote and will be ready to receive pull requests or use as source to create the tags. diff --git a/package.json b/package.json index feb8b6147e..f68d3601c7 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,6 @@ "name": "wazuh", "version": "4.5.0", "revision": "01", - "stage": "stable", - "commit": "c805cbcd0", "pluginPlatform": { "version": "2.6.0" }, @@ -41,6 +39,8 @@ "test:browser": "plugin-helpers test:browser", "test:jest": "node scripts/jest", "generate:api-4.0-info": "cd scripts/generate-api-4.0-info;./generate-api-4.0-info.sh;cd ../..", + "release:bump": "node scripts/release/bump --manifest-package package.json --manifest-plugin opensearch_dashboards.json", + "release:tag": "node scripts/release/tag --manifest-package package.json", "prebuild": "node scripts/generate-build-version" }, "dependencies": { @@ -84,4 +84,4 @@ "tslint": "^5.11.0", "typescript-eslint-parser": "^18.0.0" } -} \ No newline at end of file +} diff --git a/public/package.test.ts b/public/package.test.ts index 0ee26aa170..b7986f4910 100644 --- a/public/package.test.ts +++ b/public/package.test.ts @@ -29,22 +29,3 @@ describe('package.json revison', () => { expect(revisionIsNaN).toBeFalsy(); }); }); - -describe('package.json stage', () => { - it('should have a stage', () => { - expect(packageValue.stage).toBeDefined(); - }); - it('the state should be one of the defined.', () => { - const stateDefined = [ - 'pre-alpha', - 'alpha', - 'beta', - 'release-candidate', - 'stable', - ]; - - const stage = packageValue.stage; - - expect(stateDefined).toContain(stage); - }); -}); diff --git a/scripts/release/bump.js b/scripts/release/bump.js new file mode 100644 index 0000000000..68fa65b65c --- /dev/null +++ b/scripts/release/bump.js @@ -0,0 +1,304 @@ +// NodeJS script which receives a version, revision and/or platform version and updates +// the package.json file +// Usage: node bump.js +// Help: node bump.js --help +// Examples: node bump.js --examples + +// Process +// 1. Take values from stdin +// 2. Edit the package and plugin manifest files +// 3. Display warning about manual changes + +const cliName = 'bump'; +const cliDescription = `Bump the plugin version, revision and/or platform version +Some warning messages are sent to stderr.`; + +// Default configuration +const defaultConfiguration = { + displayConfiguration: false, + displayExamples: false, + displayHelp: false, + version: '', + revision: '', + platformVersion: '', + manifestPackage: '', + manifestPlugin: '', +}; + +const logger = require('./lib/logger'); + +const { readPackageManifest } = require('./lib/read-manifest-package'); +const { updatePackageManifest } = require('./lib/update-manifest-package'); +const { updatePluginManifest } = require('./lib/update-manifest-plugin'); + +/** + * + * @param {String[]} input Input parameters + * @returns {Object} the configuration values + */ +function parse(input) { + const configuration = {}; + // Parse the input parameters + while (input.length) { + // Extract the first parameter + const [parameter] = input.splice(0, 1); + + // Compare the parameter + switch (parameter) { + case '--debug': + // Set the logger to debug mode + logger.setLevel(0); + break; + case '--display-configuration': + // Display the configuration + configuration.displayConfiguration = true; + break; + case '--examples': + // Display the examples + configuration.displayExamples = true; + break; + case '--help': + // Display the help + configuration.displayHelp = true; + break; + case '--version': { + // Set the version + const version = typeof input[0] === 'string' && input[0]; + + if (version) { + if (/\d+\.\d+\.\d+/.test(version)) { + configuration.version = version; + input.splice(0, 1); + } else { + logger.error( + 'version parameter is not valid. Expected format: X.Y.Z where X,Y, and Z are numbers.', + ); + process.exit(1); + } + } else { + logger.error('version parameter is not defined.'); + process.exit(1); + } + break; + } + case '--revision': { + // Set the version + const revision = typeof input[0] === 'string' && input[0]; + + if (revision) { + if (/\d{2}/.test(revision)) { + configuration.revision = revision; + input.splice(0, 1); + } else { + logger.error( + 'revision parameter is not valid. Expected format: Number', + ); + process.exit(1); + } + } else { + logger.error('revision parameter is not defined.'); + process.exit(1); + } + break; + } + case '--platform-version': { + // Set the version + const platformVersion = typeof input[0] === 'string' && input[0]; + + if (platformVersion) { + if (/\d+\.\d+\.\d+/.test(platformVersion)) { + configuration.platformVersion = platformVersion; + input.splice(0, 1); + } else { + logger.error( + 'platform-version parameter is not valid. Expected format: X.Y.Z where X,Y, and Z are numbers.', + ); + process.exit(1); + } + } else { + logger.error('platform-version parameter is not defined.'); + process.exit(1); + } + break; + } + case '--manifest-package': { + // Set the version + const manifestPackage = typeof input[0] === 'string' && input[0]; + + if (manifestPackage) { + configuration.manifestPackage = manifestPackage; + input.splice(0, 1); + } else { + logger.error('manifest-package parameter is not defined.'); + process.exit(1); + } + break; + } + case '--manifest-plugin': { + // Set the version + const manifestPlugin = typeof input[0] === 'string' && input[0]; + + if (manifestPlugin) { + configuration.manifestPlugin = manifestPlugin; + input.splice(0, 1); + } else { + logger.error('manifest-plugin parameter is not defined.'); + process.exit(1); + } + break; + } + default: { + } + } + } + return configuration; +} + +const usageOptionsMessage = `Options: + --debug Set the logger to debug mode. + --display-configuration Display the configuration. Log to sterr. + --examples Display examples of usage. + --help Display the help. + --manifest-package Set the package manifest file location. + --manifest-plugin Set the plugin platform manifest file location. + --platform-version Set the platform version. + --revision Set the revision. + --version Set the version.`; + +/** + * Display the CLI help + */ +function displayHelp() { + console.log(`${cliName} - Help +${cliDescription} + +Usage: node ${cliFilePath} [options] + +${usageOptionsMessage} +`); +} + +/** + * Display the examples + */ +function displayExamples() { + console.log(` +- Change the plugin version +node ${cliFilePath} --manifest-package package.json --manifest-plugin opensearch_dashboards.json --version 4.5.0 + +- Change the plugin revision +node ${cliFilePath} --manifest-package package.json --manifest-plugin opensearch_dashboards.json --revision 02 --manifest-package ../package.json + +- Change the platform version +node ${cliFilePath} --manifest-package package.json --manifest-plugin opensearch_dashboards.json --platform-version 2.8.0 + +- Change the plugin version, revision and platform version +node ${cliFilePath} --manifest-package package.json --manifest-plugin opensearch_dashboards.json --version 4.5.0 --revision 02 --platform-version 2.8.0 +`); +} + +// Display the message to do manual changes +function displayMessageManualChanges() { + logger.warn( + 'Some files could require to do changes manually. See RELEASING.md.', + ); +} + +function run(configuration) { + const { + version, + revision, + platformVersion, + manifestPackage, + manifestPlugin, + } = configuration; + version && logger.info(`Version: ${version}`); + revision && logger.info(`Revision: ${revision}`); + platformVersion && logger.info(`Platform version: ${platformVersion}`); + manifestPackage && logger.info(`Package manifest: ${manifestPackage}`); + manifestPlugin && logger.info(`Plugin manifest: ${manifestPlugin}`); + + logger.info( + 'This will update the manifest files: package and platform plugin.', + ); + + updatePackageManifest(manifestPackage, { + version, + revision, + platformVersion, + }); + + updatePluginManifest(manifestPlugin, { + version, + revision, + }); + + displayMessageManualChanges(); +} + +function main(input) { + try { + // Display the help information and exit if there is no parameters + if (input.length === 0) { + displayHelp(); + process.exit(1); + } + + const configuration = { + ...defaultConfiguration, + ...parse(input), + }; + + // Display the configuration + if (configuration.displayConfiguration) { + /* Send to stderr. This does the configuration can be displayed and redirect the stdout output + to a file */ + console.error(configuration); + } + + // Display the help + if (configuration.displayHelp) { + displayHelp(); + process.exit(0); + } + + // Display the examples of usage + if (configuration.displayExamples) { + displayExamples(); + process.exit(0); + } + + // Check version is set + if (!configuration.version || !configuration.revision) { + const { version: manifestVersion, revision: manifestRevision } = + readPackageManifest(configuration.manifestPackage); + if (!configuration.version) { + logger.warn( + `version is not defined. Using from the current package manifest ${configuration.manifestPackage}: ${manifestVersion}`, + ); + configuration.version = manifestVersion; + } + if (!configuration.revision) { + logger.warn( + `revision is not defined. Using from the current package manifest ${configuration.manifestPackage}: ${manifestRevision}`, + ); + configuration.revision = manifestRevision; + } + } + + run(configuration); + } catch (error) { + logger.error(`An unexpected error: ${error}. ${error.stack}`); + process.exit(1); + } +} + +module.exports = run; + +let cliFilePath = __dirname; + +if (require.main === module) { + cliFilePath = process.argv[1]; + const consoleInputParameters = [...process.argv].slice(2); + main(consoleInputParameters); +} diff --git a/scripts/release/lib/logger.js b/scripts/release/lib/logger.js new file mode 100644 index 0000000000..17659895e3 --- /dev/null +++ b/scripts/release/lib/logger.js @@ -0,0 +1,20 @@ +let minimumLevel = 1; + +function setLevel(level) { + minimumLevel = level; +} + +function createLogLevel(tag, level, fn) { + return function (message) { + level >= minimumLevel && fn(`[${tag}]: ${message}`); + }; +} + +module.exports = { + info: createLogLevel('INFO', 2, console.log), + warn: createLogLevel('WARN', 1, console.log), + error: createLogLevel('ERROR', 2, console.log), + debug: createLogLevel('DEBUG', 0, console.log), + getLevel: () => minimumLevel, + setLevel: setLevel, +}; diff --git a/scripts/release/lib/read-manifest-package.js b/scripts/release/lib/read-manifest-package.js new file mode 100644 index 0000000000..e4bdbbff2f --- /dev/null +++ b/scripts/release/lib/read-manifest-package.js @@ -0,0 +1,20 @@ +const logger = require('./logger'); + +function readPackageManifest(manifestPath) { + if (!manifestPath) { + logger.error( + `package manifest file is not defined. Use --manifest-package .`, + ); + process.exit(1); + } + const fs = require('fs'); + logger.debug(`Reading file ${manifestPath}`); + const packageJson = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + logger.debug(`Read file ${manifestPath}: ${JSON.stringify(packageJson)}`); + + return packageJson; +} + +module.exports = { + readPackageManifest: readPackageManifest, +}; diff --git a/scripts/release/lib/update-manifest-package.js b/scripts/release/lib/update-manifest-package.js new file mode 100644 index 0000000000..a3e7490a0b --- /dev/null +++ b/scripts/release/lib/update-manifest-package.js @@ -0,0 +1,36 @@ +const logger = require('./logger'); + +function updatePackageManifest( + manifestPath, + { version, revision, platformVersion }, +) { + if (!manifestPath) { + logger.error( + `package manifest file is not defined. Use --manifest-package .`, + ); + process.exit(1); + } + const fs = require('fs'); + logger.debug(`Reading file ${manifestPath}`); + const packageJson = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + logger.debug(`Read file ${manifestPath}: ${JSON.stringify(packageJson)}`); + version && + (packageJson.version = version) && + logger.debug(`Change version to ${packageJson.version}`); + revision && + (packageJson.revision = revision) && + logger.debug(`Change revision to ${packageJson.revision}`); + platformVersion && + (packageJson.pluginPlatform.version = platformVersion) && + logger.debug( + `Change platform version to ${packageJson.pluginPlatform.version}`, + ); + + logger.debug(`Updating ${manifestPath}: ${JSON.stringify(packageJson)}`); + fs.writeFileSync(manifestPath, JSON.stringify(packageJson, null, 2)); + logger.info(`Updated ${manifestPath}`); +} + +module.exports = { + updatePackageManifest: updatePackageManifest, +}; diff --git a/scripts/release/lib/update-manifest-plugin.js b/scripts/release/lib/update-manifest-plugin.js new file mode 100644 index 0000000000..dcacfebe9b --- /dev/null +++ b/scripts/release/lib/update-manifest-plugin.js @@ -0,0 +1,35 @@ +const logger = require('./logger'); + +function updatePluginManifest(manifestPath, { version, revision }) { + if (!manifestPath) { + logger.error( + `plugin manifest file is not defined. Use --manifest-plugin .`, + ); + process.exit(1); + } + + if (!version) { + logger.error(`version is not defined. Use --version .`); + process.exit(1); + } + + if (!revision) { + logger.error(`revision is not defined. Use --revision .`); + process.exit(1); + } + + const fs = require('fs'); + logger.debug(`Reading file ${manifestPath}`); + const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + logger.debug(`Read file ${manifestPath}: ${JSON.stringify(manifest)}`); + manifest.version = `${version}-${revision}`; + logger.debug(`Change version to: ${manifestPath}: ${manifest.version}`); + + logger.debug(`Updating ${manifestPath}: ${JSON.stringify(manifest)}`); + fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)); + logger.info(`Updated ${manifestPath}`); +} + +module.exports = { + updatePluginManifest: updatePluginManifest, +}; diff --git a/scripts/release/tag.js b/scripts/release/tag.js new file mode 100644 index 0000000000..fc24479f7d --- /dev/null +++ b/scripts/release/tag.js @@ -0,0 +1,482 @@ +// NodeJS script which creates the tags in local and remote for the supported versions. +// It receives a version, revision and/or platform version and for each +// supported version: updates the package and plugin manifest files and create the tag. +// Usage: node tag.js +// Help: node tag.js --help +// Examples: node tag.js --examples + +// Process +// 1. Parse stdin +// 2. For each supported platform version +// 2.1. Checkout to the platform base branch +// 2.2. Prune local branches and tags +// 2.3. Edit the package manifest file +// 2.4. Edit the plugin manifest file +// 2.5. Commit +// 2.6. Create tag +// 2.7. Push tag +// 2.8. Reset local branch to remote branch +// 3. Optional. Export tags to file + +const cliName = 'tag'; +const cliDescription = `Create the tags in remote repository for the supported versions. +Some warning messages are sent to stderr.`; + +// Default configuration +const defaultConfiguration = { + displayConfiguration: false, + displayExamples: false, + displayHelp: false, + version: '', + revision: '', + platformVersion: '', + ignoreConfirmation: false, + manifestPackage: '', + manifestPlugin: '', + tagSuffix: '', + exportTagsToFile: '', +}; + +const logger = require('./lib/logger'); + +const { readPackageManifest } = require('./lib/read-manifest-package'); +const bump = require('./bump'); +const readline = require('readline'); + +// Supported versions +function getSupportedVersions(pluginVersion) { + return { + Kibana: { + branch: `${pluginVersion}-7.16`, + versions: [ + ...[...Array(4).keys()].map(idx => `7.16.${idx}`), + ...[...Array(11).keys()].map(idx => `7.17.${idx}`), + ], + manifestPluginPath: 'kibana.json', + }, + OpenDistro: { + branch: `${pluginVersion}-7.10`, + versions: ['7.10.2'], + manifestPluginPath: 'kibana.json', + }, + OpenSearch: { + branch: pluginVersion, + versions: ['2.6.0'], + manifestPluginPath: 'opensearch_dashboards.json', + }, + }; +} + +/** + * + * @param {String[]} input Input parameters + * @returns {Object} the configuration values + */ +function parse(input) { + const configuration = {}; + // Parse the input parameters + while (input.length) { + // Extract the first parameter + const [parameter] = input.splice(0, 1); + + // Compare the parameter + switch (parameter) { + case '--debug': + // Set the logger to debug mode + logger.setLevel(0); + break; + case '--display-configuration': + // Display the configuration + configuration.displayConfiguration = true; + break; + case '--export-tags': { + // Export tags to file + const exportTagsToFile = typeof input[0] === 'string' && input[0]; + if (exportTagsToFile) { + configuration.exportTagsToFile = exportTagsToFile; + input.splice(0, 1); + } else { + logger.error('export-tags parameter is not defined.'); + process.exit(1); + } + break; + } + case '--examples': + // Display the examples + configuration.displayExamples = true; + break; + case '--help': + // Display the help + configuration.displayHelp = true; + break; + case '--ignore-confirmation': + // Display the help + configuration.ignoreConfirmation = true; + break; + case '--version': { + // Set the version + const version = typeof input[0] === 'string' && input[0]; + + if (version) { + if (/\d+\.\d+\.\d+/.test(version)) { + configuration.version = version; + input.splice(0, 1); + } else { + logger.error( + 'version parameter is not valid. Expected format: X.Y.Z where X,Y, and Z are numbers.', + ); + process.exit(1); + } + } else { + logger.error('version parameter is not defined.'); + process.exit(1); + } + break; + } + case '--revision': { + // Set the version + const revision = typeof input[0] === 'string' && input[0]; + + if (revision) { + if (/\d{2}/.test(revision)) { + configuration.revision = revision; + input.splice(0, 1); + } else { + logger.error( + 'revision parameter is not valid. Expected format: Number', + ); + process.exit(1); + } + } else { + logger.error('revision parameter is not defined.'); + process.exit(1); + } + break; + } + case '--platform-version': { + // Set the version + const platformVersion = typeof input[0] === 'string' && input[0]; + + if (platformVersion) { + if (/\d+\.\d+\.\d+/.test(platformVersion)) { + configuration.platformVersion = platformVersion; + input.splice(0, 1); + } else { + logger.error( + 'platform-version parameter is not valid. Expected format: X.Y.Z where X,Y, and Z are numbers.', + ); + process.exit(1); + } + } else { + logger.error('platform-version parameter is not defined.'); + process.exit(1); + } + break; + } + case '--tag-suffix': { + // Set the version + const tagSuffix = typeof input[0] === 'string' && input[0]; + + if (tagSuffix) { + configuration.tagSuffix = tagSuffix; + input.splice(0, 1); + } else { + logger.error('tag-suffix parameter is not defined.'); + process.exit(1); + } + break; + } + case '--manifest-package': { + // Set the version + const manifestPackage = typeof input[0] === 'string' && input[0]; + + if (manifestPackage) { + configuration.manifestPackage = manifestPackage; + input.splice(0, 1); + } else { + logger.error('manifest-package parameter is not defined.'); + process.exit(1); + } + break; + } + case '--manifest-plugin': { + // Set the version + const manifestPlugin = typeof input[0] === 'string' && input[0]; + + if (manifestPlugin) { + configuration.manifestPlugin = manifestPlugin; + input.splice(0, 1); + } else { + logger.error('manifest-plugin parameter is not defined.'); + process.exit(1); + } + break; + } + default: { + } + } + } + return configuration; +} + +const usageOptionsMessage = `Options: + --debug Set the logger to debug mode. + --display-configuration Display the configuration. Log to sterr. + --examples Display examples of usage. + --export-tags Export tags to file. + --help Display the help. + --ignore-confirmation Ignore the confirmation. + --manifest-package Set the package manifest file location. + --manifest-plugin Set the plugin platform manifest file location. + --platform-version Set the platform version. + --revision Set the revision. + --tag-suffix Set the tag suffix. + --version Set the version.`; + +/** + * Display the CLI help + */ +function displayHelp() { + console.log(`${cliName} - Help +${cliDescription} + +Usage: node ${cliFilePath} [options] + +${usageOptionsMessage} +`); +} + +/** + * Display the examples + */ +function displayExamples() { + console.log(` +- Change the plugin version +node ${cliFilePath} --manifest-package package.json --manifest-plugin opensearch_dashboards.json --version 4.5.0 + +- Change the plugin revision +node ${cliFilePath} --manifest-package package.json --manifest-plugin opensearch_dashboards.json --revision 02 + +- Change the platform version +node ${cliFilePath} --manifest-package package.json --manifest-plugin opensearch_dashboards.json --platform-version 2.8.0 + +- Change the plugin version, revision and platform version +node ${cliFilePath} --manifest-package package.json --manifest-plugin opensearch_dashboards.json --version 4.5.0 + +- Change plugin revision and export tags to file +node ${cliFilePath} --manifest-package package.json --manifest-plugin opensearch_dashboards.json --export-tags tags.log --revision 02 + +- Change plugin revision and ignoring the confirmation +node ${cliFilePath} --manifest-package package.json --manifest-plugin opensearch_dashboards.json --ignore-confirmation --revision 02 + +- Change plugin revision and redirect the stdout and stderr to a file +node ${cliFilePath} --manifest-package package.json --manifest-plugin opensearch_dashboards.json --ignore-confirmation --revision 02 &> /path/to/create_tags.log + +- Change plugin revision, redirect the stdout and stderr to a file and export tags to file +node ${cliFilePath} --manifest-package package.json --manifest-plugin opensearch_dashboards.json --ignore-confirmation --revision 02 --export-tags tags.log &> /path/to/create_tags.log +`); +} + +async function question(question) { + return new Promise(res => { + const rd = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + rd.question(question, input => { + rd.close(); + res(input); + }); + }); +} + +async function requireConfirmation({ ignoreConfirmation }) { + logger.warn( + 'Ensure the base branches are created in the remote and they have updated the files: ' + + 'README.md, CHANGELOG.md, unit tests files, API data files. ' + + 'It does not modify these files.', + ); + + logger.warn( + 'This script will commit and push the tags to the remote repository, ' + + 'deleting any unpushed changes.', + ); + + if (!ignoreConfirmation) { + const response = await question('Do you want to continue? [y/N] '); + if (response.toLowerCase() !== 'y') { + logger.info('Aborting...'); + process.exit(0); + } + } +} + +async function run(configuration) { + let { + version, + revision, + platformVersion, + tagSuffix, + exportTagsToFile, + ignoreConfirmation, + } = configuration; + if (!version || !revision) { + const { version: manifestVersion, revision: manifestRevision } = + readPackageManifest(configuration.manifestPackage); + if (!version) { + logger.warn( + `version is not defined. Using from the current package manifest ${configuration.manifestPackage}: ${manifestVersion}`, + ); + configuration.version = version = manifestVersion; + } + if (!revision) { + logger.warn( + `revision is not defined. Using from the current package manifest ${configuration.manifestPackage}: ${manifestRevision}`, + ); + configuration.revision = revision = manifestRevision; + } + } + version && logger.info(`Version: ${version}`); + revision && logger.info(`Revision: ${revision}`); + platformVersion && logger.info(`Platform version: ${platformVersion}`); + tagSuffix && logger.info(`Tag suffix: ${tagSuffix}`); + exportTagsToFile && logger.info(`Export tags to file: ${exportTagsToFile}`); + + const supportedVersions = getSupportedVersions(version); + logger.info('Supported platforms'); + for (const platformID in supportedVersions) { + const { + branch, + versions: pluginPlatformVersions, + manifestPluginPath, + } = supportedVersions[platformID]; + logger.info( + `Platform: ${platformID} - Base branch: ${branch} - Versions: [${pluginPlatformVersions.join( + ', ', + )}] - Manifest plugin path: ${manifestPluginPath}`, + ); + } + + await requireConfirmation({ ignoreConfirmation }); + + const { execSync } = require('child_process'); + + function execSystem(command) { + logger.info(`Run command: ${command}`); + return execSync(command); + } + + for (const platformID in supportedVersions) { + const { + branch, + versions: pluginPlatformVersions, + manifestPluginPath, + } = supportedVersions[platformID]; + + for (const pluginPlatformVersion of pluginPlatformVersions) { + logger.debug(`Switching to branch: ${branch}`); + execSystem(`git checkout ${branch}`); + logger.info(`Switched to branch: ${branch}`); + logger.debug('Pruning local branches and tags'); + execSystem('git fetch --prune --prune-tags'); + logger.info('Pruned local branches and tags'); + + const tag = `v${version}-${pluginPlatformVersion}${tagSuffix}`; + logger.info(`Generating tag: ${tag}...`); + logger.info('Calling to bump script'); + const configurationBump = { + ...configuration, + manifestPlugin: manifestPluginPath, + platformVersion: pluginPlatformVersion, + }; + logger.debug( + `Configuration to use with the bump script: ${configurationBump}`, + ); + + bump(configurationBump); + + logger.debug('Checking if there are changes to commit'); + const thereChangesToCommit = + execSystem('git diff --exit-code --no-patch;echo -n $?').toString() === + '1'; + logger.debug(`Are there changes to commit?: ${thereChangesToCommit}`); + + if (thereChangesToCommit) { + logger.info('There are changes to commit.'); + logger.debug('Commiting'); + execSystem(`git commit -am "Bump ${tag}"`); + logger.info('Commited'); + } else { + logger.info('There are not changes to commit.'); + } + + logger.debug(`Creating tag: ${tag}`); + execSystem( + `git tag -a ${tag} -m "Wazuh ${version} for ${platformID} ${pluginPlatformVersion}"`, + ); + logger.info(`Created tag: ${tag}`); + logger.debug(`Pushing tag ${tag} to remote`); + execSystem(`git push origin ${tag}`); + logger.info(`Pushed tag ${tag} to remote`); + logger.debug('Undoing changes'); + execSystem(`git reset --hard origin/${branch}`); + logger.info('Undone changes'); + } + } + + if (exportTagsToFile) { + logger.debug(`Exporting tags to file ${exportTagsToFile}`); + execSystem( + `git tag | grep -P -i "^v${version}-.*${tagSuffix}" > ${exportTagsToFile}`, + ); + logger.info(`Exported tags to file ${exportTagsToFile}`); + } +} + +async function main(input) { + try { + // Display the help information and exit if there is no parameters + if (input.length === 0) { + displayHelp(); + process.exit(1); + } + + const configuration = { + ...defaultConfiguration, + ...parse(input), + }; + + // Display the configuration + if (configuration.displayConfiguration) { + console.error(configuration); // Send to stderr. This does the configuration can be displayed and redirect the stdout output to a file + } + + // Display the help + if (configuration.displayHelp) { + displayHelp(); + process.exit(0); + } + + // Display the examples of usage + if (configuration.displayExamples) { + displayExamples(); + process.exit(0); + } + + await run(configuration); + } catch (error) { + logger.error(`An unexpected error: ${error}. ${error.stack}`); + process.exit(1); + } +} + +module.exports = run; + +let cliFilePath; + +if (require.main === module) { + cliFilePath = process.argv[1]; + const consoleInputParameters = [...process.argv].slice(2); + main(consoleInputParameters); +} diff --git a/scripts/tag.py b/scripts/tag.py deleted file mode 100644 index 4e88781012..0000000000 --- a/scripts/tag.py +++ /dev/null @@ -1,151 +0,0 @@ -import json -import logging -import os -import subprocess - -# ==================== CONFIGURATION ==================== # -# Fill the variables below with the desired values -# -# Values to modify: -# - version - sent to the package.json -# - revision - sent to the package.json -# - stage - sent to the package.json -# - tag_suffix - used by the tag generation -# - supported_versions & kbn_versions ONLY IF NEEDED (e.g. new Kibana version) -# ======================================================= # - -# Wazuh version: major.minor.patch -version = '4.5.0' -# App's revision number (previous rev + 1) -revision = '02' -# One of 'pre-alpha', 'alpha', 'beta', 'release-candidate', 'stable' -stage = 'stable' -# Tag suffix. Usually set to stage + stage iteration. -tag_suffix = '-alpha1' - -# ================================================ # -# Constants and global variables # -# ================================================ # -LOG_FILE = 'output.log' -TAGS_FILE = 'tags.log' -# Global variable. Will be set later -branch = None -minor = version - -# Supported versions of Kibana -kbn_versions = [ - [f'7.16.{x}' for x in range(0, 4)], - [f'7.17.{x}' for x in range(0, 10)] -] - -# Platforms versions -supported_versions = { - 'OpenDistro': { - 'branch': f'{minor}-7.10', - 'versions': ['7.10.2'] - }, - 'Kibana': { - 'branch': f'{minor}-7.16', - # Flatten 2D list kbn_versions using lists comprehension - 'versions': [item for sublist in kbn_versions for item in sublist] - }, - 'Wazuh Dashboard': { - 'branch': f'{minor}', - 'versions': ['2.6.0'] - } -} - -# ================================================ # -# Functions # -# ================================================ # - -def require_confirmation(): - """Ask for confirmation before running the script.""" - print('WARNING! This script will commit and push the tags to the remote ' - + 'repository, deleting any unpushed changes.') - confirmation = input('Do you want to continue? [y/N] ') - - if confirmation.lower() != 'y': - logging.info('Aborting...') - exit(0) - - -def get_git_revision_short_hash() -> str: - return subprocess.check_output(['git', 'rev-parse', '--short', branch]).decode('ascii').strip() - - -def update_package_json(v: str) -> tuple: - """Update package.json with the new version and revision.""" - logging.info(f'Updating package.json') - data, success = {}, True - - # Read JSON and update keys. - with open('package.json', 'r') as f: - data, success = json.load(f), False - - # Update file - data['commit'] = get_git_revision_short_hash() - data['version'] = version - data['revision'] = revision - data['stage'] = stage - data['pluginPlatform']['version'] = v - - with open('package.json', 'w') as f: - json.dump(data, f, indent=2) - - os.system('node scripts/generate-build-version') - - return data, success - - -def setup(): - """Sync the repo.""" - logging.info( - f'Switching to branch "{branch}" and removing outdated tags...') - os.system(f'git checkout {branch}') - os.system('git fetch --prune --prune-tags') - - -def main(platform: str, versions: list): - """Main function.""" - for v in versions: - # if stage == 'stable': - # pass # skipped as we have been asked to - # tag = f'v{version}-{v}' - # else: - tag = f'v{version}-{v}{tag_suffix}' - logging.info(f'Generating tag "{tag}"') - update_package_json(v) - os.system(f'git commit -am "Bump {tag}"') - os.system( - f'git tag -a {tag} -m "Wazuh {version} for {platform} {v}"') - logging.info(f'Pushing tag "{tag}" to remote.') - os.system(f'git push origin {tag}') - # Undo latest commit - os.system(f'git reset --hard origin/{branch}') - - # Save created tags to file - os.system(f'git tag | grep -P -i "^v{version}-.*-{tag_suffix}" > {TAGS_FILE}') - -# ================================================ # -# Main program # -# ================================================ # - -if __name__ == '__main__': - logging.basicConfig( - filename=LOG_FILE, - level=logging.INFO, - format='%(asctime)s %(message)s' - ) - logging.info( - f'Wazuh version is "{version}". App revision is "{revision}". Stage is "{stage}"') - require_confirmation() - - for platform_name, platform_data in supported_versions.items(): - branch, versions = platform_data['branch'], platform_data['versions'] - setup() - main(platform_name, versions) - - - print(f'\nCOMPLETED. \nCheck {LOG_FILE} for more details.') - print(f'Tags are stored in {TAGS_FILE}')