stevedlawrence commented on code in PR #1432: URL: https://github.com/apache/daffodil/pull/1432#discussion_r1953300528
########## .github/actions/asf-release-candidate/main.js: ########## @@ -0,0 +1,196 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const fs = require("fs"); +const os = require("os"); +const core = require("@actions/core"); +const github = require("@actions/github"); +const { exec } = require('@actions/exec'); + +async function run() { + try { + const tlp_dir = core.getInput("tlp_dir", { required: true }); + const project_id = core.getInput("project_id", { required: true }); + const project_dir = core.getInput("project_dir"); + const gpg_signing_key = core.getInput("gpg_signing_key", { required: true }); + const svn_username = core.getInput("svn_username", { required: true }); + const svn_password = core.getInput("svn_password", { required: true }); + const nexus_username = core.getInput("nexus_username", { required: true }); + const nexus_password = core.getInput("nexus_password", { required: true }); + let publish = core.getBooleanInput("publish"); + + // import signing key into gpg and get it's key id + let gpg_import_stdout = "" + await exec("gpg", ["--batch", "--import", "--import-options", "import-show"], { + input: Buffer.from(gpg_signing_key), + listeners: { + stdout: (data) => { gpg_import_stdout += data.toString(); } + } + }); + const gpg_signing_key_id = gpg_import_stdout.match("[0-9A-Z]{40}")[0]; + console.info("Using gpgp key id: " + gpg_signing_key_id); + + // tags must be signed with a commiters key, download and import committer + // keys for verification later + let committer_keys = ""; + await exec("curl", [`https://downloads.apache.org/${ tlp_dir }/KEYS`], { + silent: true, + listeners: { + stdout: (data) => { committer_keys += data.toString(); } + } + }); + await exec("gpg", ["--batch", "--import"], { + input: Buffer.from(committer_keys) + }); + + // get the actual project version from the source build configuration--this + // does not have a leading 'v' or -rcX suffix, but might have a -SNAPSHOT + // suffix. Note that regex stuff is a bit specific Daffodil projects. We + // may want to consider requiring projects using this to have a VERSION + // file, and it's up to projects to keep that file in sync with the build + // configuration--most configs can probably just read this file. This is + // really the only part of this action that is specific to Daffodil. + // Everything else would likely work for other ASF projects. + let project_version = ""; + if (fs.existsSync("package.json")) { + project_version = fs.readFileSync("package.json").toString().match(/"version": "(.*)"/)[1]; + } else if (fs.existsSync("build.sbt")) { + project_version = fs.readFileSync("build.sbt").toString().match(/version := "(.*)"/)[1]; + } else { + throw new Error("Could not determine project version from package.json or build.sbt"); + } + + let release_version = ""; + if (github.context.eventName == "push" && github.context.ref.startsWith("refs/tags/")) { + // this was triggered by the push of a tag, the tag name will be the + // version used + release_version = github.context.ref.slice("refs/tags/".length); + + // make sure the tag name matches the actual project version + if (!release_version.startsWith(`v${project_version}-`)) { + throw new Error(`Tag ${ release_version } does not match project version: v${ project_version }`); + } + + // The github checkout action does not fetch tag information when + // triggered from a tag, so we fetch it manually so we can verify its tag + await exec("git", ["fetch", "origin", "--deepen=1", `+${ github.context.ref }:${ github.context.ref }`]); + + // make sure the tag is signed by a committer in the KEYS file, this + // command fails if the tag does not verify. + await exec("git", ["tag", "--verify", release_version]); + } else { + // this was not triggered by a tag, maybe is was manually triggered via + // workflow_dispatch or a normal commit. We should only publish from tags, + // so we disable publishing. We also set the release_version so that it has the + // same format as a tag (e.g. v1.2.3-rc1) + core.warning("Action not triggered from tag, publishing disabled"); + release_version = `v${ project_version }-rc0`; + publish = false; + } + + const is_snapshot = project_version.includes("-SNAPSHOT"); + + // disable publishing for snapshot builds or non-ASF builds. Note that + // publishing could still be disabled if the publish input was explicitly set + // to false + if (publish && (is_snapshot || process.env.GITHUB_REPOSITORY_OWNER != "apache")) { + core.warning("Publishing disabled for snapshot versions and from non-apache repositories"); + publish = false; + } + + const release_dir = `${ os.tmpdir() }/release`; + fs.mkdirSync(release_dir); + + // enable and configure SBT for signing and publishing. Note that the + // sbt-pgp plugin version should not be updated unless there is a + // compelling reason. Release signing has been known to break with newer + // versions. + const sbt_dir = `${ os.homedir }/.sbt/1.0` + fs.mkdirSync(`${ sbt_dir }/plugins`, { recursive: true }); + fs.appendFileSync(`${ sbt_dir }/plugins/build.sbt`, 'addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")\n'); + fs.appendFileSync(`${ sbt_dir }/build.sbt`, `pgpSigningKey := Some("${ gpg_signing_key_id }")\n`); + + if (publish) { + // if publishing is enabled, publishing to the apache staging repository + // with the provided credentials. We must diable gigahorse since that fails + // to publish on some systems + fs.appendFileSync(`${ sbt_dir }/build.sbt`, 'ThisBuild / updateOptions := updateOptions.value.withGigahorse(false)\n'); + fs.appendFileSync(`${ sbt_dir }/build.sbt`, `ThisBuild / credentials += Credentials("Sonatype Nexus Repository Manager", "repository.apache.org", "${ nexus_username }", "${ nexus_password }")\n`); + fs.appendFileSync(`${ sbt_dir }/build.sbt`, 'ThisBuild / publishTo := Some("Apache Staging Distribution Repository" at "https://repository.apache.org/service/local/staging/deploy/maven2")\n'); + } else { + // if publishing is not enabled, we still want the ability for workflows to + // run 'sbt publishSigned' so they don't have to change logic depending on + // if they are publishing or not. To support this, configure sbt to publish + // to a local maven repo + const maven_local_dir = `${ release_dir }/maven-local`; + fs.mkdirSync(maven_local_dir); + fs.appendFileSync(`${ sbt_dir }/build.sbt`, `ThisBuild / publishTo := Some(MavenCache("maven-local", file("${ maven_local_dir }")))\n`); + } Review Comment: Note that if this were made more generic for other ASF projects, this would probably also want to add a configuration for maven/gradle/etc. I'm not sure what those configs woudl be, or if they even support global configs like SBT does, so that's also a consideration. SBT is not used that commonly in the ASF ecosystem. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
