From 2e2e968655d5c7fff27dfba1fc769a8ae0cda174 Mon Sep 17 00:00:00 2001 From: hda10527 <lukas.koenen@h-da.de> Date: Sat, 10 Apr 2021 22:41:37 +0200 Subject: [PATCH] chore(pipeline): add manual task to create a new release - add jobs to create installers Closes #6 --- .gitlab-ci.yml | 163 +++++++++++++++++++++++++++- .idea/openconnect-gui.iml | 8 +- CMakeLists.txt | 5 +- README.md | 15 --- infra/scripts/addReleaseLink.js | 71 ++++++++++++ infra/scripts/downloadRelease.js | 76 +++++++++++++ infra/scripts/downloadTapAdapter.js | 55 ++++++++++ release.config.js | 146 +++++++++++++++++++++++++ 8 files changed, 518 insertions(+), 21 deletions(-) create mode 100644 infra/scripts/addReleaseLink.js create mode 100644 infra/scripts/downloadRelease.js create mode 100644 infra/scripts/downloadTapAdapter.js create mode 100644 release.config.js diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3d94aa6..26a593d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,9 +1,20 @@ stages: - - lint + - analyze + - release + - release:create_assets + +.node_packages_install: &node_packages_install + - npm install + yargs + @gitbeaker/node + semver + node-fetch + tar + node-7z .linters: + stage: analyze image: python:rc-alpine3.13 - stage: lint before_script: - pip3 install cpplint @@ -15,3 +26,151 @@ lint_application: paths: - coverage.xml needs: [ ] + +create_release: + stage: release + image: node:lts + rules: + - if: "$CI_COMMIT_BRANCH == 'master'" + when: manual + allow_failure: false + variables: + GIT_AUTHOR_NAME: project_12589_bot + GIT_AUTHOR_EMAIL: project12589_bot@example.com + GIT_COMMITTER_NAME: project_12589_bot + GIT_COMMITTER_EMAIL: project12589_bot@example.com + before_script: + - npm install + semantic-release + @semantic-release/gitlab + @semantic-release/git + conventional-changelog-conventionalcommits + script: + - npx semantic-release + # This job has explicitly no needs, because it should + # only run if everything has successfully been executed. + +create_rpm_file: + stage: release:create_assets + image: fedora:33 + rules: + - if: "$CI_COMMIT_BRANCH == 'master'" + before_script: + - yum -y install + cmake + rpmdevtools + qt5-qtbase-devel + openconnect-devel + npm + - *node_packages_install + - useradd --create-home builder # create user -> discouraged to build packages as the root user + - su builder -c "rpmdev-setuptree" + - node $CI_PROJECT_DIR/infra/scripts/downloadRelease.js + --gitlab_host $CI_SERVER_URL + --gitlab_token $GITLAB_TOKEN + --project_id $CI_PROJECT_ID + --installer_type rpm + --output_path /home/builder/rpmbuild/SOURCES + --extract_output_path /home/builder + script: + - su builder -c "rpmbuild -bb /home/builder/openconnect-hda/rpm/openconnect-hda.spec" + - node $CI_PROJECT_DIR/infra/scripts/addReleaseLink.js + --gitlab_host $CI_SERVER_URL + --gitlab_token $GITLAB_TOKEN + --project_id $CI_PROJECT_ID + --installer_type rpm + --asset_path /home/builder/rpmbuild/RPMS/x86_64 + needs: + - job: create_release + +create_deb_file: + stage: release:create_assets + image: ubuntu:20.04 + rules: + - if: "$CI_COMMIT_BRANCH == 'master'" + before_script: + - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y + pkgconf + wget + debhelper + devscripts + libopenconnect5 + libopenconnect-dev + vpnc + qtbase5-dev + npm + - wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null + | gpg --dearmor - + | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null + - echo deb https://apt.kitware.com/ubuntu/ focal main >> /etc/apt/sources.list + - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y cmake # get current cmake version + - *node_packages_install + - useradd --create-home builder # create user -> discouraged to build packages as the root user + - node $CI_PROJECT_DIR/infra/scripts/downloadRelease.js + --gitlab_host $CI_SERVER_URL + --gitlab_token $GITLAB_TOKEN + --project_id $CI_PROJECT_ID + --installer_type deb + --output_path /home/builder + --extract_output_path /home/builder + script: + - chown -R builder /home/builder/openconnect-hda && cd /home/builder/openconnect-hda/debian + - su builder -c "debuild -us -uc" + - node $CI_PROJECT_DIR/infra/scripts/addReleaseLink.js + --gitlab_host $CI_SERVER_URL + --gitlab_token $GITLAB_TOKEN + --project_id $CI_PROJECT_ID + --installer_type deb + --asset_path /home/builder + needs: + - create_release + +create_msi_file: + stage: release:create_assets + rules: + - if: "$CI_COMMIT_BRANCH == 'master'" + variables: + ARCHIVE_URL: http://build.openvpn.net/downloads/releases/ + TAP_ADAPTER_FILENAME: tap-windows-9.24.6-I601-amd64.msm + OPENVPN_FILENAME: OpenVPN-2.5.2-I601-amd64.msi + before_script: + - *node_packages_install + - node $CI_PROJECT_DIR/infra/scripts/downloadRelease.js + --gitlab_host $CI_SERVER_URL + --gitlab_token $GITLAB_TOKEN + --project_id $CI_PROJECT_ID + --installer_type msi + - mkdir $CI_PROJECT_DIR/openconnect-hda/cmake-build-release + - cd $CI_PROJECT_DIR/openconnect-hda/cmake-build-release + - cmake -DCMAKE_BUILD_TYPE=Release -G "CodeBlocks - Unix Makefiles" .. + - cd $CI_PROJECT_DIR/openconnect-hda + - cmake --build $CI_PROJECT_DIR/openconnect-hda/cmake-build-release --target openconnect-hda + - node $CI_PROJECT_DIR/infra/scripts/downloadTapAdapter.js + --archive_url $ARCHIVE_URL + --tap_adapter_filename $TAP_ADAPTER_FILENAME + --openvpn_filename $OPENVPN_FILENAME + --output_path $CI_PROJECT_DIR/openconnect-hda/windows/tap-adapter + script: + - cd $CI_PROJECT_DIR/openconnect-hda/windows/wix + - candle + -arch x64 + -out build/ + openconnect-hda.wxs + - light + -out build/openconnect-hda.msi + -pdbout build/openconnect-hda.wixpdb + -cultures:null + -ext "WixUIExtension.dll" + -wixprojectfile openconnect-hda.wixproj + build/openconnect-hda.wixobj + - node $CI_PROJECT_DIR/infra/scripts/addReleaseLink.js + --gitlab_host $CI_SERVER_URL + --gitlab_token $GITLAB_TOKEN + --project_id $CI_PROJECT_ID + --installer_type msi + --asset_path $CI_PROJECT_DIR/openconnect-hda/windows/wix/build + needs: + - job: create_release + tags: + - win + - openconnect-hda diff --git a/.idea/openconnect-gui.iml b/.idea/openconnect-gui.iml index f08604b..e621658 100644 --- a/.idea/openconnect-gui.iml +++ b/.idea/openconnect-gui.iml @@ -1,2 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> -<module classpath="CMake" type="CPP_MODULE" version="4" /> \ No newline at end of file +<module classpath="CMake" type="CPP_MODULE" version="4"> + <component name="FacetManager"> + <facet type="Python" name="Python facet"> + <configuration sdkName="Python 3.8" /> + </facet> + </component> +</module> \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e89d2c..c4abbf7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,12 +6,11 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) +# assume default location of qt cmake files if (WIN32) - # On Windows Machines set this to the install directory of Qt set(Qt5_DIR "C:/Qt/Qt5.12.10/5.12.10/mingw73_64/lib/cmake/Qt5") elseif (UNIX) - # On Linux set this to the path where find_package() etc. should search for Qt Files - set(CMAKE_PREFIX_PATH "/usr/lib64/cmake/Qt5") + set(Qt5_DIR "/usr/lib64/cmake/Qt5") endif () set(QT_VERSION 5) diff --git a/README.md b/README.md index 3b27064..26cbb1c 100644 --- a/README.md +++ b/README.md @@ -86,21 +86,6 @@ Options: * `openconnect` development files (on some distributions additional `-devel / -dev` and `-lib` package) * `vpnc` if not installed with `openconnect` package -## History -* 0.3.1 - * switch to gnutls to fix security issue (see https://www.openssl.org/news/secadv/20210325.txt) -* 0.3.0 - * separate ui and openconnect library calls - * run ui and openconnect library calls in different threads - * refactor PopupInfo -* 0.2.0 - * Fix connect and disconnect process - * Refactor connection status update -* 0.1.1 - * Updated package dependencies -* 0.1.0 - * Working version without complete documentation and without code signing (windows) - ## Meta Lukas Koenen – lukas.koenen@h-da.de diff --git a/infra/scripts/addReleaseLink.js b/infra/scripts/addReleaseLink.js new file mode 100644 index 0000000..74fed49 --- /dev/null +++ b/infra/scripts/addReleaseLink.js @@ -0,0 +1,71 @@ +const gitlab = require("@gitbeaker/node"); +const semver = require("semver"); +const fs = require("fs") + +async function addReleaseLink(gitlabHost, gitlabToken, projectId, installerType, assetPath) { + const api = new gitlab.Gitlab({ + host: gitlabHost, + oauthToken: gitlabToken + }); + + const projectUrl = (await api.Projects.show(projectId)).web_url; + const projectName = (await api.Projects.show(projectId)).name; + const projectRelease = semver.maxSatisfying( + (await api.Releases.all(projectId)).map(release => release.tag_name), "*"); + + const linkTargetFilename = { + rpm: `${projectName}-${projectRelease}-1.fc33.x86_64.rpm`, + deb: `${projectName}_${projectRelease}-1_amd64.deb`, + msi: `${projectName}.msi` + } + const filePath = `${assetPath}/${linkTargetFilename[installerType]}`; + + const releaseLinks = (await api.ReleaseLinks.all(projectId, projectRelease)).map(link => link.tag_name); + if (!releaseLinks.includes(linkTargetFilename[installerType])) { + const uploadResponse = (await api.Projects.upload( + projectId, fs.readFileSync(filePath), { + metadata: { + filename: `${linkTargetFilename[installerType]}`, + contentType: "multipart/form-data" + } + })) + await api.ReleaseLinks.create( + projectId, projectRelease, `${linkTargetFilename[installerType]}`, `${projectUrl}${uploadResponse.url}`) + } +} + +const argv = require("yargs/yargs")(process.argv.slice(2)) + .option("gitlab_host", { + description: "Set the gitlab host", + type: "string", + required: true + }) + .option("gitlab_token", { + description: "Set the gitlab token", + type: "string", + required: true + }) + .option("project_id", { + description: "Set the gitlab project id", + type: "number", + required: true + }) + .option("installer_type", { + description: "Set the type of the asset", + choices: ["rpm", "deb", "msi"], + required: true + }) + .option("asset_path", { + description: "Set the path of the installer", + type: "string", + default: process.cwd() + }) + .showHelpOnFail(true) + .alias("help", "h") + .hide("version") + .argv; + +addReleaseLink(argv.gitlab_host, argv.gitlab_token, argv.project_id, argv.installer_type, argv.asset_path).catch((error) => { + console.log(error); + process.exit(1); +}); diff --git a/infra/scripts/downloadRelease.js b/infra/scripts/downloadRelease.js new file mode 100644 index 0000000..4f57983 --- /dev/null +++ b/infra/scripts/downloadRelease.js @@ -0,0 +1,76 @@ +const gitlab = require("@gitbeaker/node"); +const semver = require("semver"); +const fetch = require("node-fetch"); +const fs = require('fs'); +const stream = require('stream'); +const util = require('util'); +const tar = require("tar"); + +async function downloadRelease(gitlabHost, gitlabToken, projectId, installerType, outputPath, extractOutputPath) { + const api = new gitlab.Gitlab({ + host: gitlabHost, + jobToken: gitlabToken + + }); + + const projectUrl = (await api.Projects.show(projectId)).web_url; + const projectName = (await api.Projects.show(projectId)).name; + const projectRelease = semver.maxSatisfying( + (await api.Releases.all(projectId)).map(release => release.tag_name), "*"); + + const outputFilename = { + rpm: `${projectName}-${projectRelease}.tar.gz`, + deb: `${projectName}_${projectRelease}.orig.tar.gz`, + msi: `${projectName}.tar.gz` + } + + await util.promisify(stream.pipeline)( + (await fetch(`${projectUrl}/-/archive/${projectRelease}/${projectName}-${projectRelease}.tar.gz`)).body, + fs.createWriteStream(`${outputPath}/${outputFilename[installerType]}`) + ); + + await tar.extract({file: `${outputPath}/${outputFilename[installerType]}`, cwd: extractOutputPath}); + fs.renameSync(`${extractOutputPath}/${projectName}-${projectRelease}`, `${extractOutputPath}/${projectName}`); +} + +const argv = require("yargs/yargs")(process.argv.slice(2)) + .option("gitlab_host", { + description: "Set the gitlab host", + type: "string", + required: true + }) + .option("gitlab_token", { + description: "Set the gitlab token", + type: "string", + required: true + }) + .option("project_id", { + description: "Set the gitlab project id", + type: "number", + required: true + }) + .option("installer_type", { + description: "Set the type of the installer", + choices: ["rpm", "deb", "msi"], + required: true + }) + .option("output_path", { + description: "Set the output path for the release archive", + type: "string", + default: process.cwd() + }) + .option("extract_output_path", { + description: "Set output path for the uncompressed release archive", + type: "string", + default: process.cwd() + }) + .showHelpOnFail(true) + .alias("help", "h") + .hide("version") + .argv; + +downloadRelease(argv.gitlab_host, argv.gitlab_token, argv.project_id, argv.installer_type, argv.output_path, argv.extract_output_path).catch((error) => { + console.log(error); + process.exit(1); +}); + diff --git a/infra/scripts/downloadTapAdapter.js b/infra/scripts/downloadTapAdapter.js new file mode 100644 index 0000000..2a4afb4 --- /dev/null +++ b/infra/scripts/downloadTapAdapter.js @@ -0,0 +1,55 @@ +const fetch = require("node-fetch"); +const fs = require("fs"); +const stream = require("stream"); +const util = require("util"); +const seven = require("node-7z"); + +async function downloadTapAdapter(tapAdapterFilename, openvpnFilename, archiveUrl, outputPath) { + await util.promisify(stream.pipeline)( + (await fetch(`${archiveUrl}/${tapAdapterFilename}`)).body, + fs.createWriteStream(`${outputPath}/${tapAdapterFilename}`) + ); + + await util.promisify(stream.pipeline)( + (await fetch(`${archiveUrl}/${openvpnFilename}`)).body, + fs.createWriteStream(`${outputPath}/${openvpnFilename}`) + ); + + seven.extract(`${outputPath}/${openvpnFilename}`, `${outputPath}`, { + recursive: true, + $cherryPick: "tapctl.exe" + }).on("end", function () { + fs.unlinkSync(`${outputPath}/${openvpnFilename}`) + }); +} + +const argv = require("yargs/yargs")(process.argv.slice(2)) + .option("tap_adapter_filename", { + description: "Set the filename of the tap adapter merge module", + type: "string", + required: true + }) + .option("openvpn_filename", { + description: "Set the filename of openvpn installer, used to extract the tabctl utility", + type: "string", + required: true + }) + .option("archive_url", { + description: "Set the url of the file archive", + type: "string", + default: "http://build.openvpn.net/downloads/releases/" + }) + .option("output_path", { + description: "Set the output path for the files", + type: "string", + default: process.cwd() + }) + .showHelpOnFail(true) + .alias("help", "h") + .hide("version") + .argv; + +downloadTapAdapter(argv.tap_adapter_filename, argv.openvpn_filename, argv.archive_url, argv.output_path).catch((error) => { + console.log(error); + process.exit(1); +}); \ No newline at end of file diff --git a/release.config.js b/release.config.js new file mode 100644 index 0000000..c10dd6b --- /dev/null +++ b/release.config.js @@ -0,0 +1,146 @@ +"use strict"; + +module.exports = { + plugins: [ + [ + "@semantic-release/commit-analyzer", + { + preset: "conventionalcommits" + } + ], + [ + "@semantic-release/release-notes-generator", + { + preset: "conventionalcommits" + } + ], + { + prepare: (_, context) => { + const fs = require('fs') + const path = require("path"); + const dateformat = require("dateformat"); + const parser = require("conventional-commits-parser").sync; + const filter = require("conventional-commits-filter"); + + String.prototype.splice = function (index, remove, string) { + return this.slice(0, index) + string + this.slice(index + Math.abs(remove)); + }; + + const bumpFiles = ["rpm/openconnect-hda.spec", "makepkg/PKGBUILD", "windows/wix/openconnect-hda.wxs", "src/openconnect-hda.8"]; + const changelogFiles = ["debian/changelog", "rpm/openconnect-hda.spec"]; + + const parsedCommits = filter( + context.commits.filter(({message}) => + message.trim() + ).map(rawCommit => ({ + ...rawCommit, + ...parser(rawCommit.message) + })).filter(({type}) => + type === "feat" || type === "fix" + ) + ); + + const debianChangelog = [ + `openconnect-hda (${context.nextRelease.gitTag}-1) unstable; urgency=low`, + "", + ...parsedCommits.map(commit => " * " + commit.subject), + "", + ` -- ${context.env.GIT_COMMITTER_NAME} <${context.env.GIT_COMMITTER_EMAIL}> ${dateformat(new Date(), "ddd, d mmm yyyy HH:MM:ss Z").replace("GMT", "")}` + ].join("\n"); + + const rpmChangelog = [ + `* ${dateformat(new Date(), "ddd mmm d yyyy")} ${context.env.GIT_COMMITTER_NAME} <${context.env.GIT_COMMITTER_EMAIL}> - ${context.nextRelease.gitTag}-1`, + ...parsedCommits.map(commit => "- " + commit.subject) + ].join("\n"); + + const versionUpdaterByFile = { + "windows/wix/openconnect-hda.wxs"(content) { + const versionStringIndex = content.indexOf("Version="); + + const versionString = content.substring(versionStringIndex, content.indexOf("\n", versionStringIndex)); + const newVersionString = versionString.replace(context.lastRelease.gitTag, context.nextRelease.gitTag); + + return content.splice(versionStringIndex, versionString.length, newVersionString); + }, + "rpm/openconnect-hda.spec"(content) { + const versionStringIndex = content.indexOf("Version:"); + + const versionString = content.substring(versionStringIndex, content.indexOf("\n", versionStringIndex)); + const newVersionString = versionString.replace(context.lastRelease.gitTag, context.nextRelease.gitTag); + + return content.splice(versionStringIndex, versionString.length, newVersionString); + }, + "makepkg/PKGBUILD"(content) { + const versionStringIndex = content.indexOf("pkgver="); + + const versionString = content.substring(versionStringIndex, content.indexOf("\n", versionStringIndex)); + const newVersionString = versionString.replace(context.lastRelease.gitTag, context.nextRelease.gitTag); + + return content.splice(versionStringIndex, versionString.length, newVersionString); + }, + "src/openconnect-hda.8"(content) { + return content.replace(context.lastRelease.gitTag, context.nextRelease.gitTag); + } + }; + + const changelogUpdaterByFile = { + "rpm/openconnect-hda.spec"(content) { + return content.splice(content.indexOf("%changelog") + "%changelog".length, 0, "\n" + rpmChangelog + "\n"); + }, + "debian/changelog"(content) { + return content.splice(0, 0, debianChangelog + "\n\n"); + } + } + + function updateFile(updater, file) { + const configPath = path.resolve(process.cwd(), file) + try { + const stat = fs.lstatSync(configPath); + if (!stat.isFile()) { + return; + } + + let content = fs.readFileSync(configPath, 'utf8').toString(); + fs.writeFileSync(configPath, updater(content), 'utf8'); + } catch (err) { + if (err.code !== 'ENOENT') console.warn(err.message); + } + } + + bumpFiles.forEach(file => { + const updater = versionUpdaterByFile[file]; + if (!updater) { + throw Error(`Unable to locate updater for provided file (${file}).`); + } + + updateFile(updater, file); + } + ) + + changelogFiles.forEach(file => { + const updater = changelogUpdaterByFile[file]; + if (!updater) { + throw Error(`Unable to locate updater for provided file (${file}).`); + } + + updateFile(updater, file); + } + ) + } + }, + [ + "@semantic-release/git", + { + assets: ["debian/changelog", "rpm/openconnect-hda.spec", "makepkg/PKGBUILD", "windows/wix/openconnect-hda.wxs", "src/openconnect-hda.8"], + message: "release(<%= nextRelease.version %>): auto generated release version <%= nextRelease.version %> at <%= new Date().toISOString() %>\n\n<%= nextRelease.notes %>" + } + ], + [ + "@semantic-release/gitlab", + { + gitlabUrl: "https://code.fbi.h-da.de/" + } + ] + ], + tagFormat: "${version}" +} -- GitLab