This is an automated email from the ASF dual-hosted git repository.

olabusayo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil-infrastructure.git


The following commit(s) were added to refs/heads/main by this push:
     new 91847b4  Make `gpg`, `svn` and `nexus` credentials optional
91847b4 is described below

commit 91847b4f79f339f581f17050b2b586944340021e
Author: olabusayoT <[email protected]>
AuthorDate: Tue Nov 4 12:23:29 2025 -0500

    Make `gpg`, `svn` and `nexus` credentials optional
    
    - we only care about them if do_publish is true. do_publish is based on the 
value of `publish` input as well as whether it is a tag, snapshot or asf build.
    - if do_publish is true, then `gpg_signing_key` is required and it'd error 
out if not provided. If do_publish is false, we generate a key that expires in 
1 day for signing and verification (public key is stored as artifact, while 
private key is used for signing) without input from the user
    - we write the SVN credentials to the default svn config dir servers file 
~/.subversion/servers, so the post step doesn't have to worry about svn 
credentials, it can access it thanks to main step
    - update comments and documentation
    
    DAFFODIL-3041
---
 actions/release-candidate/README.md          |  42 +++-----
 actions/release-candidate/action.yml         |  12 +--
 actions/release-candidate/dist/main/index.js | 154 ++++++++++++++++++---------
 actions/release-candidate/dist/post/index.js |  25 +++--
 actions/release-candidate/src/main.js        | 154 ++++++++++++++++++---------
 actions/release-candidate/src/post.js        |  25 +++--
 6 files changed, 264 insertions(+), 148 deletions(-)

diff --git a/actions/release-candidate/README.md 
b/actions/release-candidate/README.md
index 8dff629..81e8a10 100644
--- a/actions/release-candidate/README.md
+++ b/actions/release-candidate/README.md
@@ -70,18 +70,18 @@ This is useful for testing the workflow using workflow 
dispatch.
 
 ## Inputs
 
-| Input           | Required | Default | Description |
-|-----------------|----------|---------|-------------|
-| tlp_dir         | yes      |         | Directory of the top level project in 
dist/dev/ |
-| project_name    | yes      |         | Human readable name of the project |
-| project_id      | yes      |         | ID of the project, used in source 
artifact file name |
-| project_dir     | no       | ""      | Directory for the project in 
dev/dist/<tlp_dir>/. Omit if at the root |
-| gpg_signing_key | yes      |         | Key used to sign artifacts |
-| svn_username    | yes      |         | Username for publishing release 
artifacts to SVN dev/dist |
-| svn_password    | yes      |         | Password for publishing release 
artifacts to SVN dev/dist |
-| nexus_username  | yes      |         | Username for publishing release 
artifacts to Nexus |
-| nexus_password  | yes      |         | Password for publishing release 
artifacts to Nexus |
-| publish         | no       | false   | Enable/disabling publish artifacts. 
Must be explicitly set to true to enable publishing. May be ignored depending 
on other factors. |
+| Input           | Required      | Default | Description |
+|-----------------|---------------|---------|-------------|
+| tlp_dir         | yes           |         | Directory of the top level 
project in dist/dev/ |
+| project_name    | yes           |         | Human readable name of the 
project |
+| project_id      | yes           |         | ID of the project, used in 
source artifact file name |
+| project_dir     | no            | ""      | Directory for the project in 
dev/dist/<tlp_dir>/. Omit if at the root |
+| gpg_signing_key | if publishing |         | Key used to sign artifacts |
+| svn_username    | if publishing |         | Username for publishing release 
artifacts to SVN dev/dist |
+| svn_password    | if publishing |         | Password for publishing release 
artifacts to SVN dev/dist |
+| nexus_username  | if publishing |         | Username for publishing release 
artifacts to Nexus |
+| nexus_password  | if publishing |         | Password for publishing release 
artifacts to Nexus |
+| publish         | no            | false   | Enable/disabling publish 
artifacts. Must be explicitly set to true to enable publishing. May be ignored 
depending on other factors. |
 
 ## Outputs
 
@@ -165,22 +165,11 @@ Perform the following steps to test changes to the 
daffodil-infrastructure repo
 
 1. Update the `uses` action of the `ASF Release Candidate` step in the
    `.github/workflows/release-candidate.yml` file of the repository to be 
tested on. Then, 
-    push your changes to your fork of the test repository. 
-2. Add the secrets required by the `ASF Release Candidate` step to your
-    test repository. The following secrets are required:
-
-   - DAFFODIL_GPG_SECRET_KEY (any private key without a passphrase)
-   - DAFFODIL_SVN_DEV_USERNAME
-   - DAFFODIL_SVN_DEV_PASSWORD,
-   - NEXUS_STAGE_DEPLOYER_USER
-   - NEXUS_STAGE_DEPLOYER_PW
-
-   The other secrets should be set to non-empty dummy values and not actual 
-   usernames/passwords. They will not be used.
-3.  Now you can generate a release by going to the Actions tab of your test 
fork,
+    push your changes to your fork of the test repository.
+2. Now you can generate a release by going to the Actions tab of your test 
fork,
     and selecting the workflow that uses the release-candidate action. 
     Click the `Run workflow` button, and select the branch you just pushed to.
-4.  Once the run is complete, you can download the release from the 
`Artifacts` 
+3. Once the run is complete, you can download the release from the `Artifacts` 
     tab under `Summary`. After downloading the release, extract it. 
 
 If you want to validate things with the check-release container, follow its 
steps 
@@ -194,3 +183,4 @@ podman run -it --rm \
 --volume <ARTIFACT-DIR>:/release \
 daffodil-check-release "NA" "NA" /release
 ```
+> Note: The public key needed to verify the artifacts is available in the 
release-download directory.
\ No newline at end of file
diff --git a/actions/release-candidate/action.yml 
b/actions/release-candidate/action.yml
index 2e08049..170d860 100644
--- a/actions/release-candidate/action.yml
+++ b/actions/release-candidate/action.yml
@@ -33,23 +33,23 @@ inputs:
     default: ""
   gpg_signing_key:
     description: Key used to sign artifacts
-    required: true
+    required: false
   svn_username:
     description: Username for publishing release artifacts to SVN dev/dist
-    required: true
+    required: false
   svn_password:
     description: Password for publishing release artifacts to SVN dev/dist
-    required: true
+    required: false
   nexus_username:
     description: Username for publishing release artifacts to Nexus
-    required: true
+    required: false
   nexus_password:
     description: Password for publishing release artifacts to Nexus
-    required: true
+    required: false
   publish:
     description: Enable/disabling publish artifacts. Must be explcitly set to 
true to enable publishing. Maybe ignored depending on other factors.
     required: false
-    default: false
+    default: "false"
 
 outputs:
   artifact_dir:
diff --git a/actions/release-candidate/dist/main/index.js 
b/actions/release-candidate/dist/main/index.js
index c9ce528..7b89c02 100644
--- a/actions/release-candidate/dist/main/index.js
+++ b/actions/release-candidate/dist/main/index.js
@@ -31855,46 +31855,66 @@ async function run() {
                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);
+               const publish = core.getBooleanInput("publish");
+
+               // get the actual project version, this requires a 'VERSION' 
file at
+               // the root of the repository
+               const project_version = 
fs.readFileSync("VERSION").toString().trim();
+               const is_snapshot = project_version.includes("-SNAPSHOT");
+               const is_apache = process.env.GITHUB_REPOSITORY_OWNER == 
"apache";
+               const gitTagPrefix = "refs/tags/";
+               const is_tagged = github.context.eventName == "push" && 
github.context.ref.startsWith(gitTagPrefix);
+               // The publish setting must be explicitly set to true to 
actually publish artifacts. Note that
+               // this is required but not sufficient as even if this setting 
is true, a number of other factors (usually
+               // related to testing) can disable publishing (e.g. non-tagged, 
snapshot build, non-ASF repository)
+               const do_publish = publish && is_tagged && !is_snapshot && 
is_apache
+
+               if (!do_publish) {
+                       core.warning("Publishing disabled:")
+                       if (!publish) core.warning("- Published releases must 
set the 'publish' setting to 'true'")
+                       if (!is_tagged) core.warning("- Published releases must 
be triggered via a tag")
+                       if (is_snapshot) core.warning("- Published releases 
must have non-snapshot versions")
+                       if (!is_apache) core.warning("- Published releases must 
come from an ASF repository")
+               }
+
+               // we only require the gpg_singing_key if we are actually going 
to publish artifacts. If it is
+               // not required and not provided, we generate a temporary key 
so the workflow can still
+               // sign artifacts
+               const gpg_signing_key = core.getInput("gpg_signing_key", 
{required: do_publish});
+               if (gpg_signing_key.trim() === "") {
+                       // Generate keypair (non-interactive)
+                       await exec("gpg", ["--batch", "--yes", "--passphrase", 
'', "--quick-generate-key",
+                               "Apache Daffodil Test Release 
<[email protected]>" , "default", "default", "1d"], {
+                               silent: true
+                       });
+               } else {
+                       // import signing key into gpg
+                       await exec("gpg", ["--batch", "--import", 
"--import-options", "import-show"], {
+                               input: Buffer.from(gpg_signing_key)
+                       });
+               }
 
-               // tags must be signed with a committers key, download and 
import committer
-               // keys for verification later
-               let committer_keys = "";
-               await exec("curl", [`https://downloads.apache.org/${ tlp_dir 
}/KEYS`], {
+               // Capture the key id of the most recent generated/imported key
+               let gpg_list_secret_keys_stdout = "";
+               await exec("gpg", ["--list-secret-keys", "--with-colons"], {
                        silent: true,
                        listeners: {
-                               stdout: (data) => { committer_keys += 
data.toString(); }
+                               stdout: (data) => {
+                                       gpg_list_secret_keys_stdout += 
data.toString();
+                               }
                        }
                });
-               await exec("gpg", ["--batch", "--import"], {
-                       input: Buffer.from(committer_keys)
-               });
+               const gpg_signing_key_id = gpg_list_secret_keys_stdout
+                       .split('\n')
+                       .findLast(l => l.startsWith("fpr"))
+                       .split(':')[9];
 
-               // get the actual project version, this requires a 'VERSION' 
file at
-               // the root of the repository
-               const project_version = 
fs.readFileSync("VERSION").toString().trim();
+               console.info("Using gpgp key id: " + gpg_signing_key_id);
 
                // figure out the release version. This should follow the 
pattern
                // 'v<VERSION>-rcX', where <VERSION> is the value from the 
VERSION file
-               const gitTagPrefix = "refs/tags/";
                let release_version = "";
-               if (github.context.eventName == "push" && 
github.context.ref.startsWith(gitTagPrefix)) {
+               if (is_tagged) {
                        // this was triggered by the push of a tag, the tag 
name will be the
                        // version used
                        release_version = 
github.context.ref.slice(gitTagPrefix.length);
@@ -31908,29 +31928,32 @@ async function run() {
                        // 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]);
+                       if (do_publish) {
+                               // if publishing, tags must be signed with a 
committers key, download and import committer
+                               // keys for verification
+                               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)
+                               });
+
+                               // 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
+                       // this was not triggered by a tag, maybe it was 
manually triggered via
+                       // workflow_dispatch or a normal commit. 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;
-               }
-
                // the name of the directory where we store release artifacts 
doesn't actually
                // matter since it is never published anywhere. The one time 
where this isn't true
                // is if publishing is disabled, in which case this directory 
is made available as
@@ -31950,14 +31973,41 @@ async function run() {
                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`);
 
-               // enable SBT for publishing SBOM
+               // enable SBT for publishing SBOM either locally or remotely
                fs.appendFileSync(`${ sbt_dir }/plugins/build.sbt`, 
'addSbtPlugin("com.github.sbt" %% "sbt-sbom" % "0.4.0")\n');
                fs.appendFileSync(`${ sbt_dir }/build.sbt`, 'bomFormat := 
"xml"\n');
 
-               if (publish) {
+               if (do_publish) {
+                       // if publishing is enabled, we must configure sbt to 
write to a config file for
+                       // post to read from
+                       const svn_username = core.getInput("svn_username", { 
required: true });
+                       const svn_password = core.getInput("svn_password", { 
required: true });
+
+                       // Create the default config directory if it doesn't 
exist
+                       const svn_config_dir = `${ os.homedir }/.subversion`;
+                       fs.mkdirSync(`${ svn_config_dir }`, { recursive: true 
});
+
+                       // Write to/Overwrite the 'servers' file inside it
+                       const servers_file = path.join(svn_config_dir, 
'servers');
+                       const servers_content = `
+[global]
+store-plaintext-passwords = yes
+store-plaintext-creds = yes
+
+[groups]
+default = *
+
+[default]
+username = ${svn_username}
+password = ${svn_password}
+`;
+                       fs.writeFileSync(servers_file, servers_content.trim(), 
{ mode: 0o600 });
+
                        // 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
+                       const nexus_username = core.getInput("nexus_username", 
{ required: true });
+                       const nexus_password = core.getInput("nexus_password", 
{ required: true });
                        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');
@@ -32017,7 +32067,7 @@ async function run() {
                // export state information for the post step
                core.saveState("artifact_dir", artifact_dir);
                core.saveState("gpg_signing_key_id", gpg_signing_key_id);
-               core.saveState("publish", publish);
+               core.saveState("do_publish", do_publish);
                core.saveState("release_version", release_version);
 
        } catch (error) {
diff --git a/actions/release-candidate/dist/post/index.js 
b/actions/release-candidate/dist/post/index.js
index 3337e6d..84cb73c 100644
--- a/actions/release-candidate/dist/post/index.js
+++ b/actions/release-candidate/dist/post/index.js
@@ -125521,12 +125521,10 @@ const { exec } = __nccwpck_require__(95236);
 async function run() {
        try {
                const project_name = core.getInput("project_name", { required: 
true });
-               const svn_username = core.getInput("svn_username", { required: 
true });
-               const svn_password = core.getInput("svn_password", { required: 
true });
 
                const artifact_dir = core.getState("artifact_dir");
                const gpg_signing_key_id = core.getState("gpg_signing_key_id");
-               const publish = core.getState("publish") === "true";
+               const do_publish = core.getState("do_publish") === "true";
                const release_version = core.getState("release_version");
 
                // sign/checksum all artifacts
@@ -125551,9 +125549,9 @@ async function run() {
                        }
                }
 
-               if (publish) {
+               if (do_publish) {
                        await exec("svn", ["add", artifact_dir]);
-                       await exec("svn", ["commit", "--username", 
svn_username, "--password", svn_password, "--message", `Stage ${ project_name } 
${ release_version }`, artifact_dir]);
+                       await exec("svn", ["commit", "--message", `Stage ${ 
project_name } ${ release_version }`, artifact_dir]);
                } else {
                        // if publishing was disabled then this action was 
likely just triggered
                        // just for testing, so upload the maven-local and 
artifact directories so
@@ -125561,6 +125559,21 @@ async function run() {
                        // release-download directory since it could contain 
files that already
                        // exist in the SVN checkout and were not artifacts 
created by this action
                        const release_dir = `${ os.tmpdir() }/release-download`;
+
+                       const public_key_file = `${ release_dir 
}/public-key.asc`;
+                       // if publishing is disabled, store public key as 
artifact so it can be downloaded
+                       // by the post step for verification
+                       let public_key = "";
+                       await exec("gpg", ["--armor", "--export", 
gpg_signing_key_id], {
+                               silent: true,
+                               listeners: {
+                                       stdout: (data) => {
+                                               public_key +=  data.toString();
+                                       }
+                               }
+                       });
+                       fs.appendFileSync(`${ public_key_file }`, public_key);
+
                        const svn_artifacts = fs.readdirSync(artifact_dir, { 
recursive: true, withFileTypes: true });
                        const maven_artifacts = fs.readdirSync(`${ release_dir 
}/maven-local`, { recursive: true, withFileTypes: true });
                        const upload_artifacts = [...svn_artifacts, 
...maven_artifacts]
@@ -125568,7 +125581,7 @@ async function run() {
                                .filter((dirent) => 
!dirent.parentPath.split("/").includes(".svn"))
                                .map((dirent) => `${ dirent.parentPath }/${ 
dirent.name }`);
                        const artifact_client = new DefaultArtifactClient();
-                       artifact_client.uploadArtifact("release-download", 
upload_artifacts, os.tmpdir(), {
+                       artifact_client.uploadArtifact("release-download", 
[...upload_artifacts, public_key_file], os.tmpdir(), {
                                compressionLevel: 0,
                                retentionDays: 1
                        });
diff --git a/actions/release-candidate/src/main.js 
b/actions/release-candidate/src/main.js
index 274dea8..815b59c 100644
--- a/actions/release-candidate/src/main.js
+++ b/actions/release-candidate/src/main.js
@@ -26,46 +26,66 @@ async function run() {
                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);
+               const publish = core.getBooleanInput("publish");
+
+               // get the actual project version, this requires a 'VERSION' 
file at
+               // the root of the repository
+               const project_version = 
fs.readFileSync("VERSION").toString().trim();
+               const is_snapshot = project_version.includes("-SNAPSHOT");
+               const is_apache = process.env.GITHUB_REPOSITORY_OWNER == 
"apache";
+               const gitTagPrefix = "refs/tags/";
+               const is_tagged = github.context.eventName == "push" && 
github.context.ref.startsWith(gitTagPrefix);
+               // The publish setting must be explicitly set to true to 
actually publish artifacts. Note that
+               // this is required but not sufficient as even if this setting 
is true, a number of other factors (usually
+               // related to testing) can disable publishing (e.g. non-tagged, 
snapshot build, non-ASF repository)
+               const do_publish = publish && is_tagged && !is_snapshot && 
is_apache
+
+               if (!do_publish) {
+                       core.warning("Publishing disabled:")
+                       if (!publish) core.warning("- Published releases must 
set the 'publish' setting to 'true'")
+                       if (!is_tagged) core.warning("- Published releases must 
be triggered via a tag")
+                       if (is_snapshot) core.warning("- Published releases 
must have non-snapshot versions")
+                       if (!is_apache) core.warning("- Published releases must 
come from an ASF repository")
+               }
+
+               // we only require the gpg_singing_key if we are actually going 
to publish artifacts. If it is
+               // not required and not provided, we generate a temporary key 
so the workflow can still
+               // sign artifacts
+               const gpg_signing_key = core.getInput("gpg_signing_key", 
{required: do_publish});
+               if (gpg_signing_key.trim() === "") {
+                       // Generate keypair (non-interactive)
+                       await exec("gpg", ["--batch", "--yes", "--passphrase", 
'', "--quick-generate-key",
+                               "Apache Daffodil Test Release 
<[email protected]>" , "default", "default", "1d"], {
+                               silent: true
+                       });
+               } else {
+                       // import signing key into gpg
+                       await exec("gpg", ["--batch", "--import", 
"--import-options", "import-show"], {
+                               input: Buffer.from(gpg_signing_key)
+                       });
+               }
 
-               // tags must be signed with a committers key, download and 
import committer
-               // keys for verification later
-               let committer_keys = "";
-               await exec("curl", [`https://downloads.apache.org/${ tlp_dir 
}/KEYS`], {
+               // Capture the key id of the most recent generated/imported key
+               let gpg_list_secret_keys_stdout = "";
+               await exec("gpg", ["--list-secret-keys", "--with-colons"], {
                        silent: true,
                        listeners: {
-                               stdout: (data) => { committer_keys += 
data.toString(); }
+                               stdout: (data) => {
+                                       gpg_list_secret_keys_stdout += 
data.toString();
+                               }
                        }
                });
-               await exec("gpg", ["--batch", "--import"], {
-                       input: Buffer.from(committer_keys)
-               });
+               const gpg_signing_key_id = gpg_list_secret_keys_stdout
+                       .split('\n')
+                       .findLast(l => l.startsWith("fpr"))
+                       .split(':')[9];
 
-               // get the actual project version, this requires a 'VERSION' 
file at
-               // the root of the repository
-               const project_version = 
fs.readFileSync("VERSION").toString().trim();
+               console.info("Using gpgp key id: " + gpg_signing_key_id);
 
                // figure out the release version. This should follow the 
pattern
                // 'v<VERSION>-rcX', where <VERSION> is the value from the 
VERSION file
-               const gitTagPrefix = "refs/tags/";
                let release_version = "";
-               if (github.context.eventName == "push" && 
github.context.ref.startsWith(gitTagPrefix)) {
+               if (is_tagged) {
                        // this was triggered by the push of a tag, the tag 
name will be the
                        // version used
                        release_version = 
github.context.ref.slice(gitTagPrefix.length);
@@ -79,29 +99,32 @@ async function run() {
                        // 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]);
+                       if (do_publish) {
+                               // if publishing, tags must be signed with a 
committers key, download and import committer
+                               // keys for verification
+                               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)
+                               });
+
+                               // 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
+                       // this was not triggered by a tag, maybe it was 
manually triggered via
+                       // workflow_dispatch or a normal commit. 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;
-               }
-
                // the name of the directory where we store release artifacts 
doesn't actually
                // matter since it is never published anywhere. The one time 
where this isn't true
                // is if publishing is disabled, in which case this directory 
is made available as
@@ -121,14 +144,41 @@ async function run() {
                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`);
 
-               // enable SBT for publishing SBOM
+               // enable SBT for publishing SBOM either locally or remotely
                fs.appendFileSync(`${ sbt_dir }/plugins/build.sbt`, 
'addSbtPlugin("com.github.sbt" %% "sbt-sbom" % "0.4.0")\n');
                fs.appendFileSync(`${ sbt_dir }/build.sbt`, 'bomFormat := 
"xml"\n');
 
-               if (publish) {
+               if (do_publish) {
+                       // if publishing is enabled, we must configure sbt to 
write to a config file for
+                       // post to read from
+                       const svn_username = core.getInput("svn_username", { 
required: true });
+                       const svn_password = core.getInput("svn_password", { 
required: true });
+
+                       // Create the default config directory if it doesn't 
exist
+                       const svn_config_dir = `${ os.homedir }/.subversion`;
+                       fs.mkdirSync(`${ svn_config_dir }`, { recursive: true 
});
+
+                       // Write to/Overwrite the 'servers' file inside it
+                       const servers_file = path.join(svn_config_dir, 
'servers');
+                       const servers_content = `
+[global]
+store-plaintext-passwords = yes
+store-plaintext-creds = yes
+
+[groups]
+default = *
+
+[default]
+username = ${svn_username}
+password = ${svn_password}
+`;
+                       fs.writeFileSync(servers_file, servers_content.trim(), 
{ mode: 0o600 });
+
                        // 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
+                       const nexus_username = core.getInput("nexus_username", 
{ required: true });
+                       const nexus_password = core.getInput("nexus_password", 
{ required: true });
                        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');
@@ -188,7 +238,7 @@ async function run() {
                // export state information for the post step
                core.saveState("artifact_dir", artifact_dir);
                core.saveState("gpg_signing_key_id", gpg_signing_key_id);
-               core.saveState("publish", publish);
+               core.saveState("do_publish", do_publish);
                core.saveState("release_version", release_version);
 
        } catch (error) {
diff --git a/actions/release-candidate/src/post.js 
b/actions/release-candidate/src/post.js
index 9da3f9e..45663b8 100644
--- a/actions/release-candidate/src/post.js
+++ b/actions/release-candidate/src/post.js
@@ -28,12 +28,10 @@ const { exec } = require('@actions/exec');
 async function run() {
        try {
                const project_name = core.getInput("project_name", { required: 
true });
-               const svn_username = core.getInput("svn_username", { required: 
true });
-               const svn_password = core.getInput("svn_password", { required: 
true });
 
                const artifact_dir = core.getState("artifact_dir");
                const gpg_signing_key_id = core.getState("gpg_signing_key_id");
-               const publish = core.getState("publish") === "true";
+               const do_publish = core.getState("do_publish") === "true";
                const release_version = core.getState("release_version");
 
                // sign/checksum all artifacts
@@ -58,9 +56,9 @@ async function run() {
                        }
                }
 
-               if (publish) {
+               if (do_publish) {
                        await exec("svn", ["add", artifact_dir]);
-                       await exec("svn", ["commit", "--username", 
svn_username, "--password", svn_password, "--message", `Stage ${ project_name } 
${ release_version }`, artifact_dir]);
+                       await exec("svn", ["commit", "--message", `Stage ${ 
project_name } ${ release_version }`, artifact_dir]);
                } else {
                        // if publishing was disabled then this action was 
likely just triggered
                        // just for testing, so upload the maven-local and 
artifact directories so
@@ -68,6 +66,21 @@ async function run() {
                        // release-download directory since it could contain 
files that already
                        // exist in the SVN checkout and were not artifacts 
created by this action
                        const release_dir = `${ os.tmpdir() }/release-download`;
+
+                       const public_key_file = `${ release_dir 
}/public-key.asc`;
+                       // if publishing is disabled, store public key as 
artifact so it can be downloaded
+                       // by the post step for verification
+                       let public_key = "";
+                       await exec("gpg", ["--armor", "--export", 
gpg_signing_key_id], {
+                               silent: true,
+                               listeners: {
+                                       stdout: (data) => {
+                                               public_key +=  data.toString();
+                                       }
+                               }
+                       });
+                       fs.appendFileSync(`${ public_key_file }`, public_key);
+
                        const svn_artifacts = fs.readdirSync(artifact_dir, { 
recursive: true, withFileTypes: true });
                        const maven_artifacts = fs.readdirSync(`${ release_dir 
}/maven-local`, { recursive: true, withFileTypes: true });
                        const upload_artifacts = [...svn_artifacts, 
...maven_artifacts]
@@ -75,7 +88,7 @@ async function run() {
                                .filter((dirent) => 
!dirent.parentPath.split("/").includes(".svn"))
                                .map((dirent) => `${ dirent.parentPath }/${ 
dirent.name }`);
                        const artifact_client = new DefaultArtifactClient();
-                       artifact_client.uploadArtifact("release-download", 
upload_artifacts, os.tmpdir(), {
+                       artifact_client.uploadArtifact("release-download", 
[...upload_artifacts, public_key_file], os.tmpdir(), {
                                compressionLevel: 0,
                                retentionDays: 1
                        });

Reply via email to