From 7795f40010009d2412d50e3f0ec5fca5b1b8618b Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Tue, 9 Jun 2026 12:43:14 +0530 Subject: [PATCH 01/46] feat(action): Create PR for SAST AVIATOR changed files --- .../generic_action/actions/zip/create-pr.yaml | 87 +++++ .../helper/ci/github/ActionGitHubRepo.java | 11 +- .../helper/ci/gitlab/ActionGitLabProject.java | 11 +- .../helper/git/ActionGitSpelFunctions.java | 368 ++++++++++++++++++ .../runner/ActionRunnerContextLocal.java | 2 + .../cli/common/ci/github/GitHubRepo.java | 26 +- .../cli/common/ci/gitlab/GitLabProject.java | 25 +- .../fod/actions/zip/apply-remediations.yaml | 39 ++ .../com/fortify/cli/fod/actions/zip/ci.yaml | 14 + .../ssc/actions/zip/apply-remediations.yaml | 47 +++ .../com/fortify/cli/ssc/actions/zip/ci.yaml | 25 +- 11 files changed, 647 insertions(+), 8 deletions(-) create mode 100644 fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml create mode 100644 fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java create mode 100644 fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/apply-remediations.yaml create mode 100644 fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/apply-remediations.yaml diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml new file mode 100644 index 00000000000..52400f3e48e --- /dev/null +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml @@ -0,0 +1,87 @@ +# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json +author: Fortify +usage: + header: Create a pull request from changed files + description: | + This action detects local file changes, creates a new branch, commits the changes, + pushes to the remote, and raises a pull/merge request via the detected CI system's API. + + This action requires: + - Git repository access (for branch/commit/push operations) + - CI system token with PR/MR creation permissions (GITHUB_TOKEN, CI_JOB_TOKEN, etc.) + + Configuration via environment variables: + - `SOURCE_DIR` — Source directory where changes are detected (default: CI workspace or current directory) + - `PR_TITLE` — PR/MR title (default: "fix: Fortify auto-remediation fixes") + - `PR_BODY` — PR/MR body (default: auto-generated description) + - `PR_BRANCH_PREFIX` — Branch name prefix (default: "fcli/remediation") + - `PR_COMMIT_MESSAGE` — Commit message (default: "fix: apply Fortify auto-remediation fixes") + +config: + output: immediate + mcp: exclude + +steps: + - run.fcli: + detect-env: + group: detect-env + cmd: fcli action run detect-env + status.check: true + + - var.set: + sourceDir: ${#env('SOURCE_DIR')?:global.ci.sourceDir?:'.'} + baseBranch: "${#git.defaultBranch(#env('SOURCE_DIR')?:global.ci.sourceDir?:'.')?:global.ci.sourceBranch?:'main'}" + + # Check if there are changes to commit + - var.set: + hasChanges: ${#git.hasChanges(sourceDir)} + + - if: ${!hasChanges} + do: + - log.info: "No changes detected, skipping PR creation." + - exit: 0 + + - var.set: + branchPrefix: ${#env('PR_BRANCH_PREFIX')?:'aviator/remediations'} + commitMessage: "${#env('PR_COMMIT_MESSAGE')?:'fix: apply Fortify auto-remediation fixes'}" + prTitle: "${#env('PR_TITLE')?:'fix: Fortify auto-remediation fixes'}" + prBody: "${#env('PR_BODY')?:'This pull request contains auto-remediation fixes generated by Fortify SAST Aviator.'}" + + # Create a new branch for the changes + - var.set: + branchName: ${#git.createBranch(sourceDir, branchPrefix)} + - log.info: "Created branch: ${branchName}" + + # Stage and commit changes + - var.set: + staged: ${#git.addAll(sourceDir)} + commitSha: "${#git.commit(sourceDir, commitMessage)}" + - log.info: "Committed changes: ${commitSha}" + + # Push branch to remote + - var.set: + pushedRef: ${#git.push(sourceDir)} + - log.info: "Pushed branch to remote: ${pushedRef}" + + # Create PR/MR based on detected CI system + - var.set: + ci.detected: ${#_ci.detect()} + ci.type: ${ci.detected.type} + + # GitHub PR creation + - if: ${ci.type=='github'} + do: + - var.set: + pr: ${#_ci.detect().repo().createPullRequest(prTitle, branchName, baseBranch, prBody)} + - log.info: "Created GitHub Pull Request #${pr.number}: ${pr.html_url}" + + # GitLab MR creation + - if: ${ci.type=='gitlab'} + do: + - var.set: + mr: ${#_ci.detect().project().createMergeRequest(prTitle, branchName, baseBranch, prBody)} + - log.info: "Created GitLab Merge Request !${mr.iid}: ${mr.web_url}" + + # Unknown CI system - log warning + - if: ${ci.type!='github' && ci.type!='gitlab'} + log.warn: "PR/MR creation not supported for CI system '${ci.type}'. Branch '${branchName}' has been pushed; please create a PR/MR manually." diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/github/ActionGitHubRepo.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/github/ActionGitHubRepo.java index 33f8e720fe5..12a320da932 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/github/ActionGitHubRepo.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/github/ActionGitHubRepo.java @@ -68,7 +68,16 @@ public ObjectNode addPrComment( return repo.createPullRequestComment(env.pullRequest().id(), body); } - @SpelFunction(cat=ci, desc="(PREVIEW) Adds a review comment on a specific file and line in the pull request detected from the workflow run. This function is not yet used by any built-in fcli actions; signature and implementation may change in future fcli versions based on new insights as to how to best integrate this functionality into fcli built-in actions.", + @SpelFunction(cat=ci, desc="Creates a pull request in the repository detected from the current workflow run.", + returns="Created pull request object from GitHub API") + public ObjectNode createPullRequest( + @SpelFunctionParam(name="title", desc="pull request title") String title, + @SpelFunctionParam(name="head", desc="branch containing the changes") String head, + @SpelFunctionParam(name="base", desc="branch to merge into") String base, + @SpelFunctionParam(name="body", desc="pull request description (Markdown supported)") String body) { + return repo.createPullRequest(title, head, base, body); + } + @SpelFunction(cat=ci, desc="(PREVIEW) Adds a review comment on a specific file and line in the pull request detected from the workflow run. This function is not yet used by any built-in fcli actions; signature and implementation may change in future fcli versions based on new insights as to how to best integrate this functionality into fcli built-in actions.", returns="Created review comment object") public ObjectNode addReviewComment( @SpelFunctionParam(name="path", desc="file path relative to repository root") String path, diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/gitlab/ActionGitLabProject.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/gitlab/ActionGitLabProject.java index 6a39e04140b..3a1b492fd26 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/gitlab/ActionGitLabProject.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/gitlab/ActionGitLabProject.java @@ -69,7 +69,16 @@ public ObjectNode addMrComment( return project.createMergeRequestNote(env.pullRequest().id(), body); } - private String requirePipelineId(String operation) { + @SpelFunction(cat=ci, desc="Creates a merge request in the project detected from the current pipeline run.", + returns="Created merge request object from GitLab API") + public ObjectNode createMergeRequest( + @SpelFunctionParam(name="title", desc="merge request title") String title, + @SpelFunctionParam(name="sourceBranch", desc="branch containing the changes") String sourceBranch, + @SpelFunctionParam(name="targetBranch", desc="branch to merge into") String targetBranch, + @SpelFunctionParam(name="description", desc="merge request description (Markdown supported)") String description) { + return project.createMergeRequest(title, sourceBranch, targetBranch, description); + } + private String requirePipelineId(String operation) { var pipelineId = env.pipelineId(); if (StringUtils.isBlank(pipelineId)) { throw new FcliSimpleException( diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java new file mode 100644 index 00000000000..f215b4fbe12 --- /dev/null +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java @@ -0,0 +1,368 @@ +/* + * Copyright 2021-2026 Open Text. + * + * The only warranties for products and services of Open Text + * and its affiliates and licensors ("Open Text") are as may + * be set forth in the express warranty statements accompanying + * such products and services. Nothing herein should be construed + * as constituting an additional warranty. Open Text shall not be + * liable for technical or editorial errors or omissions contained + * herein. The information contained herein is subject to change + * without notice. + */ +package com.fortify.cli.common.action.helper.git; + +import static com.fortify.cli.common.spel.fn.descriptor.annotation.SpelFunction.SpelFunctionCategory.util; + +import java.io.IOException; +import java.nio.file.Path; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.formkiq.graalvm.annotations.Reflectable; +import com.fortify.cli.common.ci.CiBranch; +import com.fortify.cli.common.ci.CiCommit; +import com.fortify.cli.common.ci.CiCommitId; +import com.fortify.cli.common.ci.CiCommitMessage; +import com.fortify.cli.common.ci.CiPerson; +import com.fortify.cli.common.ci.CiRepository; +import com.fortify.cli.common.ci.CiRepositoryName; +import com.fortify.cli.common.exception.FcliSimpleException; +import com.fortify.cli.common.json.JsonHelper; +import com.fortify.cli.common.spel.fn.descriptor.annotation.SpelFunction; +import com.fortify.cli.common.spel.fn.descriptor.annotation.SpelFunctionParam; +import com.fortify.cli.common.spel.fn.descriptor.annotation.SpelFunctionPrefix; +import com.fortify.cli.common.util.EnvHelper; + +import lombok.extern.slf4j.Slf4j; + +/** + * SpEL functions for performing Git operations on a local repository. + * Provides functionality for checking working tree status, creating branches, + * staging files, committing, and pushing changes to a remote. + * + * Available via the {@code #git} SpEL variable in action YAML files. + * + * @author Sangamesh Vijayakumar + */ +@Reflectable +@SpelFunctionPrefix("git.") +@Slf4j +public class ActionGitSpelFunctions { + public static final ActionGitSpelFunctions INSTANCE = new ActionGitSpelFunctions(); + + @SpelFunction(cat=util, desc=""" + Returns basic information about the local git repository for the given source directory, or null if the + directory is not inside a git working tree. Only constant-time lookups are performed (HEAD commit only). + Structure: + { + repository: { workspaceDir, remoteUrl?, name: { short, full? } }, + branch: { full?, short? }, + commit: { + id: { full, short }, + message: { short, full }, + author: { name, email, when }, + committer: { name, email, when } + } + } + """, returns="Git repository information or null if not a git work dir") + public ObjectNode localRepo( + @SpelFunctionParam(name="sourceDir", desc="directory assumed to be inside a git working tree") String sourceDir) { + if (StringUtils.isBlank(sourceDir)) { return null; } + var dir = Path.of(sourceDir).toAbsolutePath().normalize().toFile(); + if (!dir.exists()) { return null; } + FileRepositoryBuilder builder = new FileRepositoryBuilder().findGitDir(dir); + if (builder.getGitDir() == null) { return null; } + try (Repository repo = builder.build()) { + var mapper = JsonHelper.getObjectMapper(); + + var remote = selectRemote(repo); + var remoteUrl = remote == null ? null : repo.getConfig().getString("remote", remote, "url"); + var names = deriveRepoNames(dir.getName(), remoteUrl); + var repository = CiRepository.builder() + .workspaceDir(repo.getWorkTree().getAbsolutePath()) + .remoteUrl(StringUtils.isBlank(remoteUrl) ? null : remoteUrl) + .name(CiRepositoryName.builder() + .short_(names[0]) + .full(names[1]) + .build()) + .build(); + + CiBranch branch = null; + try { + String fullBranch = repo.getFullBranch(); + if (fullBranch != null) { + branch = CiBranch.builder() + .full(fullBranch) + .short_(Repository.shortenRefName(fullBranch)) + .build(); + } + } catch (Exception e) { /* ignore */ } + + CiCommit commit = null; + var headId = repo.resolve("HEAD"); + if (headId != null) { + try (var walk = new org.eclipse.jgit.revwalk.RevWalk(repo)) { + var gitCommit = walk.parseCommit(headId); + String shortId; + try { + var abbrev = repo.newObjectReader().abbreviate(gitCommit.getId(), 8); + shortId = abbrev.name(); + } catch (Exception ex) { + shortId = gitCommit.getId().getName().substring(0, 8); + } + + var authorIdent = gitCommit.getAuthorIdent(); + var committerIdent = gitCommit.getCommitterIdent(); + + var commitId = CiCommitId.builder() + .full(gitCommit.getId().getName()) + .short_(shortId) + .build(); + + commit = CiCommit.builder() + .headId(commitId) + .mergeId(commitId) + .message(CiCommitMessage.builder() + .short_(gitCommit.getShortMessage()) + .full(gitCommit.getFullMessage()) + .build()) + .author(authorIdent != null ? CiPerson.builder() + .name(authorIdent.getName()) + .email(authorIdent.getEmailAddress()) + .when(authorIdent.getWhenAsInstant().toString()) + .build() : null) + .committer(committerIdent != null ? CiPerson.builder() + .name(committerIdent.getName()) + .email(committerIdent.getEmailAddress()) + .when(committerIdent.getWhenAsInstant().toString()) + .build() : null) + .build(); + } catch (Exception e) { /* ignore */ } + } + + var root = mapper.createObjectNode(); + root.set("repository", mapper.valueToTree(repository)); + if (branch != null) { + root.set("branch", mapper.valueToTree(branch)); + } + if (commit != null) { + root.set("commit", mapper.valueToTree(commit)); + } + return root; + } catch (Exception e) { return null; } + } + + @SpelFunction(cat=util, desc="Checks whether the working tree of the git repository at the given directory has any uncommitted changes (modified, added, or deleted files).", + returns="`true` if there are uncommitted changes, `false` otherwise or if not a git repository") + public boolean hasChanges( + @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir) { + try (var git = openGit(sourceDir)) { + if (git == null) { return false; } + var status = git.status().call(); + return !status.getModified().isEmpty() + || !status.getAdded().isEmpty() + || !status.getRemoved().isEmpty() + || !status.getUntracked().isEmpty() + || !status.getChanged().isEmpty(); + } catch (Exception e) { + log.debug("Error checking git status", e); + return false; + } + } + + @SpelFunction(cat=util, desc="Creates a new branch in the local git repository and checks it out. The branch name is based on the provided prefix and a timestamp suffix to ensure uniqueness (e.g., 'fcli/remediation/20260520-103045').", + returns="The name of the created branch") + public String createBranch( + @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir, + @SpelFunctionParam(name="branchPrefix", desc="prefix for the branch name (e.g., 'fcli/remediation')") String branchPrefix) { + try (var git = openGit(sourceDir)) { + if (git == null) { + throw new FcliSimpleException("Not a git repository: " + sourceDir); + } + var timestamp = java.time.LocalDateTime.now() + .format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")); + var branchName = branchPrefix + "/" + timestamp; + git.checkout().setCreateBranch(true).setName(branchName).call(); + log.info("Created and checked out branch: {}", branchName); + return branchName; + } catch (GitAPIException e) { + throw new FcliSimpleException("Failed to create branch: " + e.getMessage()); + } + } + + @SpelFunction(cat=util, desc="Stages all modified and new files in the working tree for commit.", + returns="`true` if files were staged successfully") + public boolean addAll( + @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir) { + try (var git = openGit(sourceDir)) { + if (git == null) { + throw new FcliSimpleException("Not a git repository: " + sourceDir); + } + git.add().addFilepattern(".").call(); + git.add().setUpdate(true).addFilepattern(".").call(); + log.info("Staged all changes in: {}", sourceDir); + return true; + } catch (GitAPIException e) { + throw new FcliSimpleException("Failed to stage files: " + e.getMessage()); + } + } + + @SpelFunction(cat=util, desc="Commits all staged changes in the local git repository with the given message.", + returns="The commit SHA of the new commit") + public String commit( + @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir, + @SpelFunctionParam(name="message", desc="commit message") String message) { + try (var git = openGit(sourceDir)) { + if (git == null) { + throw new FcliSimpleException("Not a git repository: " + sourceDir); + } + var commitResult = git.commit() + .setMessage(message) + .setAuthor("fcli", "fcli@fortify.com") + .call(); + var sha = commitResult.getId().getName(); + log.info("Committed changes: {}", sha); + return sha; + } catch (GitAPIException e) { + throw new FcliSimpleException("Failed to commit: " + e.getMessage()); + } + } + + @SpelFunction(cat=util, desc="Pushes the current branch to the remote repository. Uses token-based authentication from CI environment variables (GITHUB_TOKEN, CI_JOB_TOKEN, SYSTEM_ACCESSTOKEN, BITBUCKET_TOKEN) if available.", + returns="The name of the remote ref that was pushed") + public String push( + @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir) { + try (var git = openGit(sourceDir)) { + if (git == null) { + throw new FcliSimpleException("Not a git repository: " + sourceDir); + } + var credentialsProvider = detectCredentialsProvider(); + if (credentialsProvider == null) { + var remoteUrl = git.getRepository().getConfig().getString("remote", "origin", "url"); + if (remoteUrl != null && remoteUrl.startsWith("https")) { + throw new FcliSimpleException("No credentials available for push to " + remoteUrl + + ". Set one of: GITHUB_TOKEN, GH_TOKEN, CI_JOB_TOKEN, SYSTEM_ACCESSTOKEN, BITBUCKET_TOKEN"); + } + } + var pushCommand = git.push(); + if (credentialsProvider != null) { + pushCommand.setCredentialsProvider(credentialsProvider); + } + var results = pushCommand.call(); + var ref = git.getRepository().getFullBranch(); + log.info("Pushed branch to remote: {}", ref); + return ref; + } catch (GitAPIException | IOException e) { + throw new FcliSimpleException("Failed to push: " + e.getMessage()); + } + } + + @SpelFunction(cat=util, desc="Detects the default branch of the remote repository. Checks CI environment variables (CI_DEFAULT_BRANCH for GitLab, looks up via GitHub API env), then falls back to reading refs/remotes/origin/HEAD from the local git config. Returns null if detection fails.", + returns="The default branch name (e.g. 'main', 'master', 'develop') or null if not detectable") + public String defaultBranch( + @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir) { + // GitLab CI provides CI_DEFAULT_BRANCH + var defaultBranch = EnvHelper.env("CI_DEFAULT_BRANCH"); + if (StringUtils.isNotBlank(defaultBranch)) { return defaultBranch; } + // Try reading from local git remote HEAD (set by git clone) + try (var git = openGit(sourceDir)) { + if (git == null) { return null; } + var repo = git.getRepository(); + var remoteHead = repo.resolve("refs/remotes/origin/HEAD"); + if (remoteHead != null) { + var ref = repo.exactRef("refs/remotes/origin/HEAD"); + if (ref != null && ref.getTarget() != null) { + var target = ref.getTarget().getName(); + // target is like refs/remotes/origin/main + if (target.startsWith("refs/remotes/origin/")) { + return target.substring("refs/remotes/origin/".length()); + } + } + } + } catch (Exception e) { + log.debug("Error detecting default branch", e); + } + return null; + } + + private Git openGit(String sourceDir) { + if (StringUtils.isBlank(sourceDir)) { return null; } + var dir = Path.of(sourceDir).toAbsolutePath().normalize().toFile(); + if (!dir.exists()) { return null; } + var builder = new FileRepositoryBuilder().findGitDir(dir); + if (builder.getGitDir() == null) { return null; } + try { + var repo = builder.build(); + return new Git(repo); + } catch (Exception e) { + return null; + } + } + + private CredentialsProvider detectCredentialsProvider() { + // GitHub Actions / GitHub CLI + var token = EnvHelper.env("GITHUB_TOKEN"); + if (StringUtils.isBlank(token)) { token = EnvHelper.env("GH_TOKEN"); } + if (StringUtils.isNotBlank(token)) { + return new UsernamePasswordCredentialsProvider("x-access-token", token); + } + // GitLab CI + token = EnvHelper.env("CI_JOB_TOKEN"); + if (StringUtils.isNotBlank(token)) { + return new UsernamePasswordCredentialsProvider("gitlab-ci-token", token); + } + // Azure DevOps + token = EnvHelper.env("SYSTEM_ACCESSTOKEN"); + if (StringUtils.isNotBlank(token)) { + return new UsernamePasswordCredentialsProvider("", token); + } + // Bitbucket Pipelines + token = EnvHelper.env("BITBUCKET_TOKEN"); + if (StringUtils.isNotBlank(token)) { + return new UsernamePasswordCredentialsProvider("x-token-auth", token); + } + return null; + } + + private static String selectRemote(Repository repo) { + try { + var remotes = repo.getRemoteNames(); + if (remotes == null || remotes.isEmpty()) { return null; } + if (remotes.contains("origin")) { return "origin"; } + return remotes.iterator().next(); + } catch (Exception e) { return null; } + } + + private static String[] deriveRepoNames(String fallbackShort, String remoteUrl) { + if (StringUtils.isBlank(remoteUrl)) { return new String[]{fallbackShort, null}; } + try { + var cleaned = remoteUrl.trim(); + if (cleaned.endsWith(".git")) { cleaned = cleaned.substring(0, cleaned.length() - 4); } + String pathPart; + if (cleaned.startsWith("git@")) { + int idx = cleaned.indexOf(":"); + pathPart = idx >= 0 ? cleaned.substring(idx + 1) : cleaned; + } else { + var uri = java.net.URI.create(cleaned); + pathPart = uri.getPath(); + if (pathPart.startsWith("/")) { pathPart = pathPart.substring(1); } + } + var parts = pathPart.split("/"); + if (parts.length >= 2) { + var shortName = parts[parts.length - 1]; + return new String[]{shortName, pathPart}; + } + return new String[]{parts[parts.length - 1], null}; + } catch (Exception e) { + return new String[]{fallbackShort, null}; + } + } +} diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/runner/ActionRunnerContextLocal.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/runner/ActionRunnerContextLocal.java index f8ab40712f0..27bf5b7acf8 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/runner/ActionRunnerContextLocal.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/runner/ActionRunnerContextLocal.java @@ -29,6 +29,7 @@ import com.fortify.cli.common.action.helper.ci.github.ActionGitHubSpelFunctions; import com.fortify.cli.common.action.helper.ci.gitlab.ActionGitLabSpelFunctions; import com.fortify.cli.common.action.helper.fs.ActionFileSystemSpelFunctions; +import com.fortify.cli.common.action.helper.git.ActionGitSpelFunctions; import com.fortify.cli.common.action.model.ActionStepCheckEntry; import com.fortify.cli.common.action.model.ActionStepCheckEntry.CheckStatus; import com.fortify.cli.common.action.model.FcliActionValidationException; @@ -237,6 +238,7 @@ protected final void configureSpelContext(SimpleEvaluationContext spelContext) { registerCiVariables(spelContext, actionRunnerContext); } spelContext.setVariable("fs", ActionFileSystemSpelFunctions.INSTANCE); + spelContext.setVariable("git", ActionGitSpelFunctions.INSTANCE); spelContext.setVariable("fcli", FcliCommandsSpelFunctions.INSTANCE); } diff --git a/fcli-core/fcli-common-ci/src/main/java/com/fortify/cli/common/ci/github/GitHubRepo.java b/fcli-core/fcli-common-ci/src/main/java/com/fortify/cli/common/ci/github/GitHubRepo.java index ee7159fd2d9..7ad54d725f7 100644 --- a/fcli-core/fcli-common-ci/src/main/java/com/fortify/cli/common/ci/github/GitHubRepo.java +++ b/fcli-core/fcli-common-ci/src/main/java/com/fortify/cli/common/ci/github/GitHubRepo.java @@ -293,7 +293,31 @@ public ObjectNode createReviewComment(String pullNumber, String commitId, .getBody(); } - // === Branch and Commit Operations === + /** + * Create a pull request. + * + * @param title Pull request title + * @param head The name of the branch where your changes are implemented + * @param base The name of the branch you want the changes pulled into + * @param body Pull request description (Markdown supported) + * @return Created pull request object + */ + public ObjectNode createPullRequest(String title, String head, String base, String body) { + var requestBody = JsonHelper.getObjectMapper().createObjectNode() + .put("title", title) + .put("head", head) + .put("base", base) + .put("body", body); + + return unirest + .post("/repos/{owner}/{repo}/pulls") + .routeParam("owner", owner) + .routeParam("repo", repo) + .body(requestBody) + .asObject(ObjectNode.class) + .getBody(); + } + // === Branch and Commit Operations === /** * Query builder for branches. diff --git a/fcli-core/fcli-common-ci/src/main/java/com/fortify/cli/common/ci/gitlab/GitLabProject.java b/fcli-core/fcli-common-ci/src/main/java/com/fortify/cli/common/ci/gitlab/GitLabProject.java index 895765256fd..f12b0790c58 100644 --- a/fcli-core/fcli-common-ci/src/main/java/com/fortify/cli/common/ci/gitlab/GitLabProject.java +++ b/fcli-core/fcli-common-ci/src/main/java/com/fortify/cli/common/ci/gitlab/GitLabProject.java @@ -134,7 +134,30 @@ public ObjectNode createMergeRequestNote(String mergeRequestIid, String body) { .getBody(); } - // === Branch and Commit Operations === + /** + * Create a merge request. + * + * @param title Merge request title + * @param sourceBranch The source branch + * @param targetBranch The target branch + * @param description Merge request description (Markdown supported) + * @return Created merge request object + */ + public ObjectNode createMergeRequest(String title, String sourceBranch, String targetBranch, String description) { + var requestBody = JsonHelper.getObjectMapper().createObjectNode() + .put("title", title) + .put("source_branch", sourceBranch) + .put("target_branch", targetBranch) + .put("description", description); + + return unirest + .post("/projects/{id}/merge_requests") + .routeParam("id", projectId) + .body(requestBody) + .asObject(ObjectNode.class) + .getBody(); + } + // === Branch and Commit Operations === /** * Query builder for branches. diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/apply-remediations.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/apply-remediations.yaml new file mode 100644 index 00000000000..d2812a9a5f7 --- /dev/null +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/apply-remediations.yaml @@ -0,0 +1,39 @@ +# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json +author: Fortify +usage: + header: Apply Aviator remediations + description: | + This action applies Aviator auto-remediation fixes to source code for a given + FoD release. It downloads the audited FPR with remediation suggestions and applies + the fixes to the local source tree. + + This action requires: + - A FoD session to be active (for downloading the audited FPR with remediations) + + After running this action, any modified files can be committed and pushed using + the `create-pr` action. + +config: + output: immediate + rest.target.default: fod + mcp: exclude + +steps: + - run.fcli: + detect-env: + group: detect-env + cmd: fcli action run detect-env + status.check: true + + - var.set: + rel: ${#env('FOD_RELEASE')?:global.ci.defaultFortifyRepo} + - if: ${rel==null} + throw: FoD release must be specified through FOD_RELEASE environment variable + + - var.set: + sourceDir: ${global.ci.sourceDir?:'.'} + + - run.fcli: + APPLY_REMEDIATIONS: + cmd: fod aviator apply-remediations --rel "${rel}" --source-dir "${sourceDir}" + status.check: true diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml index 3a48f95349c..c11c80c7297 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml @@ -135,6 +135,20 @@ steps: on.success: - var.set: { postScan.skipReason: } # Reset postScan.skipReason to allow post-scan tasks to run + APPLY_REMEDIATIONS: + cmd: ${#actionCmd('APPLY_REMEDIATIONS', 'fod', 'apply-remediations')} "--progress=none" + skip.if-reason: + - ${#actionCmdSkipFromEnvReason('APPLY_REMEDIATIONS', true)} # Skip unless DO_APPLY_REMEDIATIONS==true or APPLY_REMEDIATIONS_ACTION/EXTRA_OPTS defined + - ${postScan.skipReason} # Skip if no scans were run + - ${#env('DO_AVIATOR_AUDIT')!='true'?'Aviator audit not enabled (DO_AVIATOR_AUDIT!=true), no remediations available':''} # Skip if Aviator audit was not enabled + - ${SAST_WAIT.dependencySkipReason} # Skip if SAST scan/wait was skipped or failed + + CREATE_PR: + cmd: "${#actionCmd('CREATE_PR', '', 'create-pr')} --progress=none" + skip.if-reason: + - ${#actionCmdSkipFromEnvReason('CREATE_PR', true)} # Skip unless DO_CREATE_PR==true or CREATE_PR_EXTRA_OPTS defined + - ${APPLY_REMEDIATIONS.dependencySkipReason} # Skip if APPLY_REMEDIATIONS was skipped or failed + CHECK_POLICY: cmd: ${#actionCmd('CHECK_POLICY', 'fod', 'check-policy')} "--rel=${global.ci.rel}" "--progress=none" stdout: collect diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/apply-remediations.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/apply-remediations.yaml new file mode 100644 index 00000000000..8953b54c5dd --- /dev/null +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/apply-remediations.yaml @@ -0,0 +1,47 @@ +# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json +author: Fortify +usage: + header: Apply Aviator remediations + description: | + This action applies Aviator auto-remediation fixes to source code for a given + SSC application version. It downloads the audited FPR with remediation suggestions + and applies the fixes to the local source tree. + + This action requires: + - An SSC session to be active (for downloading the audited FPR with remediations) + - An Aviator session to be active (for the apply-remediations command) + + After running this action, any modified files can be committed and pushed using + the `create-pr` action. + +cli.options: + aviatorArtifact: + names: --aviator-artifact + description: "Artifact ID to apply remediations from. If not specified, uses --latest to apply from the most recent Aviator-processed artifact." + required: false + +config: + output: immediate + rest.target.default: ssc + mcp: exclude + +steps: + - run.fcli: + detect-env: + group: detect-env + cmd: fcli action run detect-env + status.check: true + + - var.set: + av: ${#env('SSC_APPVERSION')?:global.ci.defaultFortifyRepo} + - if: ${av==null} + throw: SSC application version must be specified through SSC_APPVERSION environment variable + + - var.set: + sourceDir: ${global.ci.sourceDir?:'.'} + artifactSelector: "${#isNotBlank(cli.aviatorArtifact) ? '--artifact-id '+cli.aviatorArtifact : '--av '+av+' --latest'}" + + - run.fcli: + APPLY_REMEDIATIONS: + cmd: "aviator ssc apply-remediations --source-dir \"${sourceDir}\" ${artifactSelector}" + status.check: true diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml index f6aa05d32ba..25d750d182a 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml @@ -195,15 +195,32 @@ steps: skip.if-reason: - ${postScan.skipReason} # Skip if no scans were run - ${aviator.skipReason} # Skip if Aviator audit is to be skipped - - + on.success: + - var.set: + aviator.artifactId: "${#var('aviator_audit').hasNonNull('artifactId') ? #var('aviator_audit').get('artifactId').asText() : ''}" + AVIATOR_WAIT: - cmd: "${#fcliCmd('AVIATOR_WAIT', 'ssc artifact wait-for')} ::aviator_audit::" + cmd: "${#fcliCmd('AVIATOR_WAIT', 'ssc artifact wait-for')} ${aviator.artifactId}" skip.if-reason: - ${postScan.skipReason} # Skip if no scans were run - ${aviator.skipReason} # Skip if Aviator audit is to be skipped - ${AVIATOR_AUDIT.dependencySkipReason} # Skip if AVIATOR_AUDIT was skipped or failed + - "${#isBlank(aviator.artifactId) ? 'No artifact produced by Aviator audit, nothing to wait for' : ''}" + APPLY_REMEDIATIONS: + cmd: ${#actionCmd('APPLY_REMEDIATIONS', 'ssc', 'apply-remediations')} --aviator-artifact ${aviator.artifactId} "--progress=none" + skip.if-reason: + - ${#actionCmdSkipFromEnvReason('APPLY_REMEDIATIONS', true)} # Skip unless DO_APPLY_REMEDIATIONS==true or APPLY_REMEDIATIONS_ACTION/EXTRA_OPTS defined + - ${postScan.skipReason} # Skip if no scans were run + - ${aviator.skipReason} # Skip if Aviator is not configured + - ${AVIATOR_WAIT.dependencySkipReason} # Skip if AVIATOR_WAIT was skipped or failed (no remediations available) + + CREATE_PR: + cmd: "${#actionCmd('CREATE_PR', '', 'create-pr')} --progress=none" + skip.if-reason: + - ${#actionCmdSkipFromEnvReason('CREATE_PR', true)} # Skip unless DO_CREATE_PR==true or CREATE_PR_EXTRA_OPTS defined + - ${APPLY_REMEDIATIONS.dependencySkipReason} # Skip if APPLY_REMEDIATIONS was skipped or failed + CHECK_POLICY: cmd: ${#actionCmd('CHECK_POLICY', 'ssc', 'check-policy')} "--av=${global.ci.av}" "--progress=none" stdout: collect @@ -283,4 +300,4 @@ formatters: ${CHECK_POLICY.exitCode==0||CHECK_POLICY.exitCode==100?CHECK_POLICY.stdout:CHECK_POLICY.dependencySkipReason} ${APPVERSION_SUMMARY.stdout} - \ No newline at end of file + From e416e0976b60e9b65ed29ddb0d7b549dac7b595d Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Tue, 9 Jun 2026 15:35:18 +0530 Subject: [PATCH 02/46] fix: Updating schema version to v2.9.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 9f171c3b906..fa61b8480f5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -51,7 +51,7 @@ fcliMainClassName=com.fortify.cli.app.FortifyCLI # given schema version, it is very important to maintain this correctly. At all cost, # we should avoid for example updating only patch version if there are any structural # changes. -fcliActionSchemaVersion=2.8.0 +fcliActionSchemaVersion=2.9.0 org.gradle.parallel=true # Ensure JDK IO subsystem is opened for all Gradle daemon JVM processes (suppresses native subprocess control warning) From 2b2f9efb000497a10e2261352c3aab74685766bf Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Wed, 10 Jun 2026 11:39:48 +0530 Subject: [PATCH 03/46] Updated the create-pr fcli action for both SSC and FoD modules, deleted the same from generic actions --- .../com/fortify/cli/fod/actions/zip/ci.yaml | 2 +- .../cli/fod}/actions/zip/create-pr.yaml | 0 .../com/fortify/cli/ssc/actions/zip/ci.yaml | 2 +- .../cli/ssc/actions/zip/create-pr.yaml | 87 +++++++++++++++++++ 4 files changed, 89 insertions(+), 2 deletions(-) rename fcli-core/{fcli-action/src/main/resources/com/fortify/cli/generic_action => fcli-fod/src/main/resources/com/fortify/cli/fod}/actions/zip/create-pr.yaml (100%) create mode 100644 fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml index c11c80c7297..5df6ecd3665 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml @@ -144,7 +144,7 @@ steps: - ${SAST_WAIT.dependencySkipReason} # Skip if SAST scan/wait was skipped or failed CREATE_PR: - cmd: "${#actionCmd('CREATE_PR', '', 'create-pr')} --progress=none" + cmd: "${#actionCmd('CREATE_PR', 'fod', 'create-pr')} --progress=none" skip.if-reason: - ${#actionCmdSkipFromEnvReason('CREATE_PR', true)} # Skip unless DO_CREATE_PR==true or CREATE_PR_EXTRA_OPTS defined - ${APPLY_REMEDIATIONS.dependencySkipReason} # Skip if APPLY_REMEDIATIONS was skipped or failed diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml similarity index 100% rename from fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml rename to fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml index 25d750d182a..967e6904b84 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml @@ -216,7 +216,7 @@ steps: - ${AVIATOR_WAIT.dependencySkipReason} # Skip if AVIATOR_WAIT was skipped or failed (no remediations available) CREATE_PR: - cmd: "${#actionCmd('CREATE_PR', '', 'create-pr')} --progress=none" + cmd: "${#actionCmd('CREATE_PR', 'ssc', 'create-pr')} --progress=none" skip.if-reason: - ${#actionCmdSkipFromEnvReason('CREATE_PR', true)} # Skip unless DO_CREATE_PR==true or CREATE_PR_EXTRA_OPTS defined - ${APPLY_REMEDIATIONS.dependencySkipReason} # Skip if APPLY_REMEDIATIONS was skipped or failed diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml new file mode 100644 index 00000000000..52400f3e48e --- /dev/null +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml @@ -0,0 +1,87 @@ +# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json +author: Fortify +usage: + header: Create a pull request from changed files + description: | + This action detects local file changes, creates a new branch, commits the changes, + pushes to the remote, and raises a pull/merge request via the detected CI system's API. + + This action requires: + - Git repository access (for branch/commit/push operations) + - CI system token with PR/MR creation permissions (GITHUB_TOKEN, CI_JOB_TOKEN, etc.) + + Configuration via environment variables: + - `SOURCE_DIR` — Source directory where changes are detected (default: CI workspace or current directory) + - `PR_TITLE` — PR/MR title (default: "fix: Fortify auto-remediation fixes") + - `PR_BODY` — PR/MR body (default: auto-generated description) + - `PR_BRANCH_PREFIX` — Branch name prefix (default: "fcli/remediation") + - `PR_COMMIT_MESSAGE` — Commit message (default: "fix: apply Fortify auto-remediation fixes") + +config: + output: immediate + mcp: exclude + +steps: + - run.fcli: + detect-env: + group: detect-env + cmd: fcli action run detect-env + status.check: true + + - var.set: + sourceDir: ${#env('SOURCE_DIR')?:global.ci.sourceDir?:'.'} + baseBranch: "${#git.defaultBranch(#env('SOURCE_DIR')?:global.ci.sourceDir?:'.')?:global.ci.sourceBranch?:'main'}" + + # Check if there are changes to commit + - var.set: + hasChanges: ${#git.hasChanges(sourceDir)} + + - if: ${!hasChanges} + do: + - log.info: "No changes detected, skipping PR creation." + - exit: 0 + + - var.set: + branchPrefix: ${#env('PR_BRANCH_PREFIX')?:'aviator/remediations'} + commitMessage: "${#env('PR_COMMIT_MESSAGE')?:'fix: apply Fortify auto-remediation fixes'}" + prTitle: "${#env('PR_TITLE')?:'fix: Fortify auto-remediation fixes'}" + prBody: "${#env('PR_BODY')?:'This pull request contains auto-remediation fixes generated by Fortify SAST Aviator.'}" + + # Create a new branch for the changes + - var.set: + branchName: ${#git.createBranch(sourceDir, branchPrefix)} + - log.info: "Created branch: ${branchName}" + + # Stage and commit changes + - var.set: + staged: ${#git.addAll(sourceDir)} + commitSha: "${#git.commit(sourceDir, commitMessage)}" + - log.info: "Committed changes: ${commitSha}" + + # Push branch to remote + - var.set: + pushedRef: ${#git.push(sourceDir)} + - log.info: "Pushed branch to remote: ${pushedRef}" + + # Create PR/MR based on detected CI system + - var.set: + ci.detected: ${#_ci.detect()} + ci.type: ${ci.detected.type} + + # GitHub PR creation + - if: ${ci.type=='github'} + do: + - var.set: + pr: ${#_ci.detect().repo().createPullRequest(prTitle, branchName, baseBranch, prBody)} + - log.info: "Created GitHub Pull Request #${pr.number}: ${pr.html_url}" + + # GitLab MR creation + - if: ${ci.type=='gitlab'} + do: + - var.set: + mr: ${#_ci.detect().project().createMergeRequest(prTitle, branchName, baseBranch, prBody)} + - log.info: "Created GitLab Merge Request !${mr.iid}: ${mr.web_url}" + + # Unknown CI system - log warning + - if: ${ci.type!='github' && ci.type!='gitlab'} + log.warn: "PR/MR creation not supported for CI system '${ci.type}'. Branch '${branchName}' has been pushed; please create a PR/MR manually." From c1381ce1ac18a2330c0c6f1bd32543decc43f7dc Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Fri, 12 Jun 2026 16:13:12 +0530 Subject: [PATCH 04/46] Enhanced logging to better catch the failures in logs --- .../cli/fod/actions/zip/create-pr.yaml | 100 ++++++++++++++++-- 1 file changed, 89 insertions(+), 11 deletions(-) diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml index 52400f3e48e..cfb541807f2 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json +# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json author: Fortify usage: header: Create a pull request from changed files @@ -27,61 +27,139 @@ steps: group: detect-env cmd: fcli action run detect-env status.check: true + on.fail: + - log.warn: + msg: "CI environment detection failed" + cause: ${detect-env_exception} - var.set: sourceDir: ${#env('SOURCE_DIR')?:global.ci.sourceDir?:'.'} baseBranch: "${#git.defaultBranch(#env('SOURCE_DIR')?:global.ci.sourceDir?:'.')?:global.ci.sourceBranch?:'main'}" + # Log initial git repository information for diagnostics + - var.set: + gitRepoInfo: ${#git.localRepo(sourceDir)} + - if: ${gitRepoInfo!=null} + log.info: "Git repository: workspace=${gitRepoInfo.repository.workspaceDir}, remote=${gitRepoInfo.repository.remoteUrl?:'not configured'}, branch=${gitRepoInfo.branch.short_?:'detached HEAD'}" + + # Log current commit information for reference (debug level) + - if: ${gitRepoInfo.commit!=null} + log.debug: "Current HEAD: ${gitRepoInfo.commit.headId.short} - ${gitRepoInfo.commit.message.short}" + # Check if there are changes to commit - var.set: hasChanges: ${#git.hasChanges(sourceDir)} - if: ${!hasChanges} do: - - log.info: "No changes detected, skipping PR creation." + - log.info: "No changes detected in ${sourceDir}, skipping PR creation." - exit: 0 + - log.info: "Changes detected in ${sourceDir}, proceeding with PR creation." + - var.set: branchPrefix: ${#env('PR_BRANCH_PREFIX')?:'aviator/remediations'} commitMessage: "${#env('PR_COMMIT_MESSAGE')?:'fix: apply Fortify auto-remediation fixes'}" prTitle: "${#env('PR_TITLE')?:'fix: Fortify auto-remediation fixes'}" prBody: "${#env('PR_BODY')?:'This pull request contains auto-remediation fixes generated by Fortify SAST Aviator.'}" - # Create a new branch for the changes + # Create a new branch for the changes with error handling - var.set: branchName: ${#git.createBranch(sourceDir, branchPrefix)} - - log.info: "Created branch: ${branchName}" + on.fail: + - log.warn: + msg: "Failed to create branch with prefix '${branchPrefix}'" + cause: ${lastException} + - throw: ${lastException} + - log.info: "Successfully created branch: ${branchName}" - # Stage and commit changes + # Stage and commit changes with error handling - var.set: staged: ${#git.addAll(sourceDir)} + on.fail: + - log.warn: + msg: "Failed to stage changes in ${sourceDir}" + cause: ${lastException} + - throw: ${lastException} + - log.debug: "All changes staged successfully" + + - var.set: commitSha: "${#git.commit(sourceDir, commitMessage)}" - - log.info: "Committed changes: ${commitSha}" + on.fail: + - log.warn: + msg: "Failed to commit staged changes with message: '${commitMessage}'" + cause: ${lastException} + - throw: ${lastException} + - log.info: "Successfully committed changes: ${commitSha}" + + # Pre-push diagnostics: Check for available credentials + - var.set: + githubToken: "${#env('GITHUB_TOKEN')?:#env('GH_TOKEN')}" + gitlabToken: "${#env('CI_JOB_TOKEN')}" + azureToken: "${#env('SYSTEM_ACCESSTOKEN')}" + bitbucketToken: "${#env('BITBUCKET_TOKEN')}" + + - log.debug: "Credential availability check - GitHub: ${githubToken!=null?'configured':'not found'}, GitLab: ${gitlabToken!=null?'configured':'not found'}, Azure: ${azureToken!=null?'configured':'not found'}, Bitbucket: ${bitbucketToken!=null?'configured':'not found'}" + + - if: "${githubToken==null && gitlabToken==null && azureToken==null && bitbucketToken==null && gitRepoInfo.repository.remoteUrl!=null && gitRepoInfo.repository.remoteUrl.contains('https')}" + do: + - log.warn: "WARNING: No CI credentials detected (GITHUB_TOKEN/GH_TOKEN, CI_JOB_TOKEN, SYSTEM_ACCESSTOKEN, BITBUCKET_TOKEN). Remote URL uses HTTPS and requires authentication: ${gitRepoInfo.repository.remoteUrl}" - # Push branch to remote + # Push branch to remote with detailed error context - var.set: pushedRef: ${#git.push(sourceDir)} - - log.info: "Pushed branch to remote: ${pushedRef}" + on.fail: + - var.set: + errorMsg: "Failed to push branch '${branchName}' to remote" + remoteUrl: "${gitRepoInfo.repository.remoteUrl?:'unknown'}" + exceptionType: "${lastException.type}" + exceptionMsg: "${lastException.message}" + - log.warn: + msg: "${errorMsg}. Remote: ${remoteUrl}. Error: ${exceptionType}: ${exceptionMsg}" + cause: ${lastException} + - if: "${exceptionMsg.contains('No credentials available')}" + do: + - log.warn: "DIAGNOSIS: Push failed due to missing credentials. Required environment variables for HTTPS remotes: GITHUB_TOKEN or GH_TOKEN (GitHub), CI_JOB_TOKEN (GitLab), SYSTEM_ACCESSTOKEN (Azure DevOps), or BITBUCKET_TOKEN (Bitbucket)" + - log.warn: "DIAGNOSIS: Ensure the CI runner has permission to set and pass through authentication tokens to this action" + - throw: + msg: "${errorMsg}" + cause: ${lastException} + - log.info: "Successfully pushed branch to remote: ${pushedRef}" # Create PR/MR based on detected CI system - var.set: ci.detected: ${#_ci.detect()} ci.type: ${ci.detected.type} + - log.debug: "Detected CI system: ${ci.type}" + # GitHub PR creation - if: ${ci.type=='github'} do: - var.set: pr: ${#_ci.detect().repo().createPullRequest(prTitle, branchName, baseBranch, prBody)} - - log.info: "Created GitHub Pull Request #${pr.number}: ${pr.html_url}" + on.fail: + - log.warn: + msg: "Failed to create GitHub Pull Request from '${branchName}' to '${baseBranch}'" + cause: ${lastException} + - throw: ${lastException} + - log.info: "Successfully created GitHub Pull Request #${pr.number}: ${pr.html_url}" # GitLab MR creation - if: ${ci.type=='gitlab'} do: - var.set: mr: ${#_ci.detect().project().createMergeRequest(prTitle, branchName, baseBranch, prBody)} - - log.info: "Created GitLab Merge Request !${mr.iid}: ${mr.web_url}" + on.fail: + - log.warn: + msg: "Failed to create GitLab Merge Request from '${branchName}' to '${baseBranch}'" + cause: ${lastException} + - throw: ${lastException} + - log.info: "Successfully created GitLab Merge Request !${mr.iid}: ${mr.web_url}" # Unknown CI system - log warning - if: ${ci.type!='github' && ci.type!='gitlab'} - log.warn: "PR/MR creation not supported for CI system '${ci.type}'. Branch '${branchName}' has been pushed; please create a PR/MR manually." + log.warn: "PR/MR creation not supported for CI system '${ci.type}'. Branch '${branchName}' (${pushedRef}) has been pushed; please create a PR/MR manually." + + # Success summary + - log.info: "PR creation workflow completed successfully. Branch '${branchName}' pushed to remote." From 8b2b22eadef334607294a01f2e936db444fe1319 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Fri, 12 Jun 2026 17:19:32 +0530 Subject: [PATCH 05/46] Fix build failures --- .../action/helper/git/ActionGitSpelFunctions.java | 11 ++++++++++- .../com/fortify/cli/fod/actions/zip/create-pr.yaml | 10 +++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java index f215b4fbe12..99ae6d6ca15 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java @@ -81,6 +81,7 @@ public ObjectNode localRepo( FileRepositoryBuilder builder = new FileRepositoryBuilder().findGitDir(dir); if (builder.getGitDir() == null) { return null; } try (Repository repo = builder.build()) { + log.debug("localRepo: Processing sourceDir={}", sourceDir); var mapper = JsonHelper.getObjectMapper(); var remote = selectRemote(repo); @@ -166,6 +167,7 @@ public boolean hasChanges( @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir) { try (var git = openGit(sourceDir)) { if (git == null) { return false; } + log.debug("hasChanges: Checking for uncommitted changes in sourceDir={}", sourceDir); var status = git.status().call(); return !status.getModified().isEmpty() || !status.getAdded().isEmpty() @@ -191,6 +193,7 @@ public String createBranch( .format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")); var branchName = branchPrefix + "/" + timestamp; git.checkout().setCreateBranch(true).setName(branchName).call(); + log.debug("createBranch: Created branch={}", branchName); log.info("Created and checked out branch: {}", branchName); return branchName; } catch (GitAPIException e) { @@ -208,6 +211,7 @@ public boolean addAll( } git.add().addFilepattern(".").call(); git.add().setUpdate(true).addFilepattern(".").call(); + log.debug("addAll: Staged all changes in sourceDir={}", sourceDir); log.info("Staged all changes in: {}", sourceDir); return true; } catch (GitAPIException e) { @@ -229,6 +233,7 @@ public String commit( .setAuthor("fcli", "fcli@fortify.com") .call(); var sha = commitResult.getId().getName(); + log.debug("commit: Committed message={}, sha={}", message, sha); log.info("Committed changes: {}", sha); return sha; } catch (GitAPIException e) { @@ -257,6 +262,7 @@ public String push( pushCommand.setCredentialsProvider(credentialsProvider); } var results = pushCommand.call(); + log.debug("push: Successfully pushed branch={} to remote", results); var ref = git.getRepository().getFullBranch(); log.info("Pushed branch to remote: {}", ref); return ref; @@ -271,7 +277,10 @@ public String defaultBranch( @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir) { // GitLab CI provides CI_DEFAULT_BRANCH var defaultBranch = EnvHelper.env("CI_DEFAULT_BRANCH"); - if (StringUtils.isNotBlank(defaultBranch)) { return defaultBranch; } + if (StringUtils.isNotBlank(defaultBranch)) { + log.debug("defaultBranch: Detected from CI_DEFAULT_BRANCH={}", defaultBranch); + return defaultBranch; + } // Try reading from local git remote HEAD (set by git clone) try (var git = openGit(sourceDir)) { if (git == null) { return null; } diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml index cfb541807f2..9e6b60c7cca 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json +# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json author: Fortify usage: header: Create a pull request from changed files @@ -27,10 +27,10 @@ steps: group: detect-env cmd: fcli action run detect-env status.check: true - on.fail: - - log.warn: - msg: "CI environment detection failed" - cause: ${detect-env_exception} + on.fail: + - log.warn: + msg: "CI environment detection failed" + cause: ${detect-env_exception} - var.set: sourceDir: ${#env('SOURCE_DIR')?:global.ci.sourceDir?:'.'} From 2f677b1bef6541743a1724e802bf53c33934ee6c Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Fri, 12 Jun 2026 20:14:12 +0530 Subject: [PATCH 06/46] Added additional loggings and baseBranch creation logic --- .../cli/fod/actions/zip/create-pr.yaml | 8 +- .../cli/ssc/actions/zip/create-pr.yaml | 104 ++++++++++++++++-- 2 files changed, 99 insertions(+), 13 deletions(-) diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml index 9e6b60c7cca..c6a9fa6ac09 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml @@ -19,6 +19,9 @@ usage: config: output: immediate + rest.target.default: fod + run.fcli.status.log.default: true # By default, we log all exit statuses + run.fcli.status.check.default: true mcp: exclude steps: @@ -34,7 +37,6 @@ steps: - var.set: sourceDir: ${#env('SOURCE_DIR')?:global.ci.sourceDir?:'.'} - baseBranch: "${#git.defaultBranch(#env('SOURCE_DIR')?:global.ci.sourceDir?:'.')?:global.ci.sourceBranch?:'main'}" # Log initial git repository information for diagnostics - var.set: @@ -130,8 +132,10 @@ steps: - var.set: ci.detected: ${#_ci.detect()} ci.type: ${ci.detected.type} - + ci.defaultBranch: ${ci.detected.repo().defaultBranch} + baseBranch: "${#env('BASE_BRANCH')?:ci.defaultBranch?:'main'}" - log.debug: "Detected CI system: ${ci.type}" + - log.debug: "Detected Base Branch: ${baseBranch}" # GitHub PR creation - if: ${ci.type=='github'} diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml index 52400f3e48e..9a9c1ba2b6e 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml @@ -19,6 +19,9 @@ usage: config: output: immediate + rest.target.default: ssc + run.fcli.status.log.default: true # By default, we log all exit statuses + run.fcli.status.check.default: true mcp: exclude steps: @@ -27,10 +30,23 @@ steps: group: detect-env cmd: fcli action run detect-env status.check: true + on.fail: + - log.warn: + msg: "CI environment detection failed" + cause: ${detect-env_exception} - var.set: sourceDir: ${#env('SOURCE_DIR')?:global.ci.sourceDir?:'.'} - baseBranch: "${#git.defaultBranch(#env('SOURCE_DIR')?:global.ci.sourceDir?:'.')?:global.ci.sourceBranch?:'main'}" + + # Log initial git repository information for diagnostics + - var.set: + gitRepoInfo: ${#git.localRepo(sourceDir)} + - if: ${gitRepoInfo!=null} + log.info: "Git repository: workspace=${gitRepoInfo.repository.workspaceDir}, remote=${gitRepoInfo.repository.remoteUrl?:'not configured'}, branch=${gitRepoInfo.branch.short_?:'detached HEAD'}" + + # Log current commit information for reference (debug level) + - if: ${gitRepoInfo.commit!=null} + log.debug: "Current HEAD: ${gitRepoInfo.commit.headId.short} - ${gitRepoInfo.commit.message.short}" # Check if there are changes to commit - var.set: @@ -38,50 +54,116 @@ steps: - if: ${!hasChanges} do: - - log.info: "No changes detected, skipping PR creation." + - log.info: "No changes detected in ${sourceDir}, skipping PR creation." - exit: 0 + - log.info: "Changes detected in ${sourceDir}, proceeding with PR creation." + - var.set: branchPrefix: ${#env('PR_BRANCH_PREFIX')?:'aviator/remediations'} commitMessage: "${#env('PR_COMMIT_MESSAGE')?:'fix: apply Fortify auto-remediation fixes'}" prTitle: "${#env('PR_TITLE')?:'fix: Fortify auto-remediation fixes'}" prBody: "${#env('PR_BODY')?:'This pull request contains auto-remediation fixes generated by Fortify SAST Aviator.'}" - # Create a new branch for the changes + # Create a new branch for the changes with error handling - var.set: branchName: ${#git.createBranch(sourceDir, branchPrefix)} - - log.info: "Created branch: ${branchName}" + on.fail: + - log.warn: + msg: "Failed to create branch with prefix '${branchPrefix}'" + cause: ${lastException} + - throw: ${lastException} + - log.info: "Successfully created branch: ${branchName}" - # Stage and commit changes + # Stage and commit changes with error handling - var.set: staged: ${#git.addAll(sourceDir)} + on.fail: + - log.warn: + msg: "Failed to stage changes in ${sourceDir}" + cause: ${lastException} + - throw: ${lastException} + - log.debug: "All changes staged successfully" + + - var.set: commitSha: "${#git.commit(sourceDir, commitMessage)}" - - log.info: "Committed changes: ${commitSha}" + on.fail: + - log.warn: + msg: "Failed to commit staged changes with message: '${commitMessage}'" + cause: ${lastException} + - throw: ${lastException} + - log.info: "Successfully committed changes: ${commitSha}" - # Push branch to remote + # Pre-push diagnostics: Check for available credentials + - var.set: + githubToken: "${#env('GITHUB_TOKEN')?:#env('GH_TOKEN')}" + gitlabToken: "${#env('CI_JOB_TOKEN')}" + azureToken: "${#env('SYSTEM_ACCESSTOKEN')}" + bitbucketToken: "${#env('BITBUCKET_TOKEN')}" + + - log.debug: "Credential availability check - GitHub: ${githubToken!=null?'configured':'not found'}, GitLab: ${gitlabToken!=null?'configured':'not found'}, Azure: ${azureToken!=null?'configured':'not found'}, Bitbucket: ${bitbucketToken!=null?'configured':'not found'}" + + - if: "${githubToken==null && gitlabToken==null && azureToken==null && bitbucketToken==null && gitRepoInfo.repository.remoteUrl!=null && gitRepoInfo.repository.remoteUrl.contains('https')}" + do: + - log.warn: "WARNING: No CI credentials detected (GITHUB_TOKEN/GH_TOKEN, CI_JOB_TOKEN, SYSTEM_ACCESSTOKEN, BITBUCKET_TOKEN). Remote URL uses HTTPS and requires authentication: ${gitRepoInfo.repository.remoteUrl}" + + # Push branch to remote with detailed error context - var.set: pushedRef: ${#git.push(sourceDir)} - - log.info: "Pushed branch to remote: ${pushedRef}" + on.fail: + - var.set: + errorMsg: "Failed to push branch '${branchName}' to remote" + remoteUrl: "${gitRepoInfo.repository.remoteUrl?:'unknown'}" + exceptionType: "${lastException.type}" + exceptionMsg: "${lastException.message}" + - log.warn: + msg: "${errorMsg}. Remote: ${remoteUrl}. Error: ${exceptionType}: ${exceptionMsg}" + cause: ${lastException} + - if: "${exceptionMsg.contains('No credentials available')}" + do: + - log.warn: "DIAGNOSIS: Push failed due to missing credentials. Required environment variables for HTTPS remotes: GITHUB_TOKEN or GH_TOKEN (GitHub), CI_JOB_TOKEN (GitLab), SYSTEM_ACCESSTOKEN (Azure DevOps), or BITBUCKET_TOKEN (Bitbucket)" + - log.warn: "DIAGNOSIS: Ensure the CI runner has permission to set and pass through authentication tokens to this action" + - throw: + msg: "${errorMsg}" + cause: ${lastException} + - log.info: "Successfully pushed branch to remote: ${pushedRef}" # Create PR/MR based on detected CI system - var.set: ci.detected: ${#_ci.detect()} ci.type: ${ci.detected.type} + ci.defaultBranch: ${ci.detected.repo().defaultBranch} + baseBranch: "${#env('BASE_BRANCH')?:ci.defaultBranch?:'main'}" + - log.debug: "Detected CI system: ${ci.type}" + - log.debug: "Detected Base Branch: ${baseBranch}" # GitHub PR creation - if: ${ci.type=='github'} do: - var.set: pr: ${#_ci.detect().repo().createPullRequest(prTitle, branchName, baseBranch, prBody)} - - log.info: "Created GitHub Pull Request #${pr.number}: ${pr.html_url}" + on.fail: + - log.warn: + msg: "Failed to create GitHub Pull Request from '${branchName}' to '${baseBranch}'" + cause: ${lastException} + - throw: ${lastException} + - log.info: "Successfully created GitHub Pull Request #${pr.number}: ${pr.html_url}" # GitLab MR creation - if: ${ci.type=='gitlab'} do: - var.set: mr: ${#_ci.detect().project().createMergeRequest(prTitle, branchName, baseBranch, prBody)} - - log.info: "Created GitLab Merge Request !${mr.iid}: ${mr.web_url}" + on.fail: + - log.warn: + msg: "Failed to create GitLab Merge Request from '${branchName}' to '${baseBranch}'" + cause: ${lastException} + - throw: ${lastException} + - log.info: "Successfully created GitLab Merge Request !${mr.iid}: ${mr.web_url}" # Unknown CI system - log warning - if: ${ci.type!='github' && ci.type!='gitlab'} - log.warn: "PR/MR creation not supported for CI system '${ci.type}'. Branch '${branchName}' has been pushed; please create a PR/MR manually." + log.warn: "PR/MR creation not supported for CI system '${ci.type}'. Branch '${branchName}' (${pushedRef}) has been pushed; please create a PR/MR manually." + + # Success summary + - log.info: "PR creation workflow completed successfully. Branch '${branchName}' pushed to remote." From c2c5b6a482f167ff8a9ba353fc7c91d2beefe9b3 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Fri, 12 Jun 2026 22:08:47 +0530 Subject: [PATCH 07/46] fixing the base branch detection logic --- .../resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml index 9a9c1ba2b6e..a57790be04e 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml @@ -132,8 +132,7 @@ steps: - var.set: ci.detected: ${#_ci.detect()} ci.type: ${ci.detected.type} - ci.defaultBranch: ${ci.detected.repo().defaultBranch} - baseBranch: "${#env('BASE_BRANCH')?:ci.defaultBranch?:'main'}" + baseBranch: "${#env('BASE_BRANCH')?:'main'}" - log.debug: "Detected CI system: ${ci.type}" - log.debug: "Detected Base Branch: ${baseBranch}" From 13186fa78e4dae07a160e77f8442cf1567582f58 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Sat, 13 Jun 2026 00:38:55 +0530 Subject: [PATCH 08/46] more logs --- .../resources/com/fortify/cli/fod/actions/zip/create-pr.yaml | 5 +++-- .../resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml index c6a9fa6ac09..9a5d240e061 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml @@ -132,20 +132,21 @@ steps: - var.set: ci.detected: ${#_ci.detect()} ci.type: ${ci.detected.type} - ci.defaultBranch: ${ci.detected.repo().defaultBranch} - baseBranch: "${#env('BASE_BRANCH')?:ci.defaultBranch?:'main'}" + baseBranch: "${#env('BASE_BRANCH')?:'main'}" - log.debug: "Detected CI system: ${ci.type}" - log.debug: "Detected Base Branch: ${baseBranch}" # GitHub PR creation - if: ${ci.type=='github'} do: + - log.debug: "Creating GitHub PR: head=${branchName}, base=${baseBranch}, title=${prTitle}" - var.set: pr: ${#_ci.detect().repo().createPullRequest(prTitle, branchName, baseBranch, prBody)} on.fail: - log.warn: msg: "Failed to create GitHub Pull Request from '${branchName}' to '${baseBranch}'" cause: ${lastException} + - log.warn: "Troubleshooting: Verify branch '${branchName}' exists on remote (git ls-remote origin ${branchName}), and base branch '${baseBranch}' is valid" - throw: ${lastException} - log.info: "Successfully created GitHub Pull Request #${pr.number}: ${pr.html_url}" diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml index a57790be04e..7c5406b1e6c 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml @@ -139,12 +139,14 @@ steps: # GitHub PR creation - if: ${ci.type=='github'} do: + - log.debug: "Creating GitHub PR: head=${branchName}, base=${baseBranch}, title=${prTitle}" - var.set: pr: ${#_ci.detect().repo().createPullRequest(prTitle, branchName, baseBranch, prBody)} on.fail: - log.warn: msg: "Failed to create GitHub Pull Request from '${branchName}' to '${baseBranch}'" cause: ${lastException} + - log.warn: "Troubleshooting: Verify branch '${branchName}' exists on remote (git ls-remote origin ${branchName}), and base branch '${baseBranch}' is valid" - throw: ${lastException} - log.info: "Successfully created GitHub Pull Request #${pr.number}: ${pr.html_url}" From 0a289517920c5800f846a4acc68512feabf1391d Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Sat, 13 Jun 2026 00:42:00 +0530 Subject: [PATCH 09/46] fixing head branch with owner --- .../resources/com/fortify/cli/fod/actions/zip/create-pr.yaml | 2 +- .../resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml index 9a5d240e061..2e99e361c4f 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml @@ -141,7 +141,7 @@ steps: do: - log.debug: "Creating GitHub PR: head=${branchName}, base=${baseBranch}, title=${prTitle}" - var.set: - pr: ${#_ci.detect().repo().createPullRequest(prTitle, branchName, baseBranch, prBody)} + pr: ${#_ci.detect().repo().createPullRequest(prTitle, #_ci.detect().repo().owner + ':' + branchName, baseBranch, prBody)} on.fail: - log.warn: msg: "Failed to create GitHub Pull Request from '${branchName}' to '${baseBranch}'" diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml index 7c5406b1e6c..25aa5f04d7a 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml @@ -141,7 +141,7 @@ steps: do: - log.debug: "Creating GitHub PR: head=${branchName}, base=${baseBranch}, title=${prTitle}" - var.set: - pr: ${#_ci.detect().repo().createPullRequest(prTitle, branchName, baseBranch, prBody)} + pr: ${#_ci.detect().repo().createPullRequest(prTitle, #_ci.detect().repo().owner + ':' + branchName, baseBranch, prBody)} on.fail: - log.warn: msg: "Failed to create GitHub Pull Request from '${branchName}' to '${baseBranch}'" From d0a5737c5c852a8ba996f8182b52386b60d09585 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Sat, 13 Jun 2026 08:33:58 +0530 Subject: [PATCH 10/46] fixing PR head for GitHub --- .../com/fortify/cli/fod/actions/zip/create-pr.yaml | 10 +++++++--- .../com/fortify/cli/ssc/actions/zip/create-pr.yaml | 12 ++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml index 2e99e361c4f..4d9c577fc72 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml @@ -139,14 +139,18 @@ steps: # GitHub PR creation - if: ${ci.type=='github'} do: - - log.debug: "Creating GitHub PR: head=${branchName}, base=${baseBranch}, title=${prTitle}" - var.set: - pr: ${#_ci.detect().repo().createPullRequest(prTitle, #_ci.detect().repo().owner + ':' + branchName, baseBranch, prBody)} + repoOwner: ${ci.detected.repo().owner} + repoName: ${ci.detected.repo().name} + prHead: "${repoOwner}:${branchName}" + - log.debug: "Creating GitHub PR: head=${prHead}, base=${baseBranch}, title=${prTitle}" + - var.set: + pr: ${#_ci.detect().repo().createPullRequest(prTitle, prHead, baseBranch, prBody)} on.fail: - log.warn: msg: "Failed to create GitHub Pull Request from '${branchName}' to '${baseBranch}'" cause: ${lastException} - - log.warn: "Troubleshooting: Verify branch '${branchName}' exists on remote (git ls-remote origin ${branchName}), and base branch '${baseBranch}' is valid" + - log.warn: "Troubleshooting: Verify branch '${branchName}' exists on remote, and base branch '${baseBranch}' is valid" - throw: ${lastException} - log.info: "Successfully created GitHub Pull Request #${pr.number}: ${pr.html_url}" diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml index 25aa5f04d7a..2f5d07f2ff2 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml @@ -139,17 +139,21 @@ steps: # GitHub PR creation - if: ${ci.type=='github'} do: - - log.debug: "Creating GitHub PR: head=${branchName}, base=${baseBranch}, title=${prTitle}" - var.set: - pr: ${#_ci.detect().repo().createPullRequest(prTitle, #_ci.detect().repo().owner + ':' + branchName, baseBranch, prBody)} + repoOwner: ${ci.detected.repo().owner} + repoName: ${ci.detected.repo().name} + prHead: "${repoOwner}:${branchName}" + - log.debug: "Creating GitHub PR: head=${prHead}, base=${baseBranch}, title=${prTitle}" + - var.set: + pr: ${#_ci.detect().repo().createPullRequest(prTitle, prHead, baseBranch, prBody)} on.fail: - log.warn: msg: "Failed to create GitHub Pull Request from '${branchName}' to '${baseBranch}'" cause: ${lastException} - - log.warn: "Troubleshooting: Verify branch '${branchName}' exists on remote (git ls-remote origin ${branchName}), and base branch '${baseBranch}' is valid" + - log.warn: "Troubleshooting: Verify branch '${branchName}' exists on remote, and base branch '${baseBranch}' is valid" - throw: ${lastException} - log.info: "Successfully created GitHub Pull Request #${pr.number}: ${pr.html_url}" - + # GitLab MR creation - if: ${ci.type=='gitlab'} do: From 920b4747844fe43e766210fe76c3eab6b600bc2f Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Sat, 13 Jun 2026 11:10:53 +0530 Subject: [PATCH 11/46] Extracting the repo owner from git --- .../resources/com/fortify/cli/fod/actions/zip/create-pr.yaml | 3 +-- .../resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml index 4d9c577fc72..70633604d9c 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml @@ -140,8 +140,7 @@ steps: - if: ${ci.type=='github'} do: - var.set: - repoOwner: ${ci.detected.repo().owner} - repoName: ${ci.detected.repo().name} + repoOwner: "${#gitRepoInfo.repository.remoteUrl.replaceAll('.*github\\.com[:/]([^/]+)/.*', '$1')}" prHead: "${repoOwner}:${branchName}" - log.debug: "Creating GitHub PR: head=${prHead}, base=${baseBranch}, title=${prTitle}" - var.set: diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml index 2f5d07f2ff2..d6de2206186 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml @@ -140,8 +140,7 @@ steps: - if: ${ci.type=='github'} do: - var.set: - repoOwner: ${ci.detected.repo().owner} - repoName: ${ci.detected.repo().name} + repoOwner: "${#gitRepoInfo.repository.remoteUrl.replaceAll('.*github\\.com[:/]([^/]+)/.*', '$1')}" prHead: "${repoOwner}:${branchName}" - log.debug: "Creating GitHub PR: head=${prHead}, base=${baseBranch}, title=${prTitle}" - var.set: From 71f3d501c1b9e6b2bfeb0cb5b0a9acf2d9481825 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Mon, 15 Jun 2026 11:33:38 +0530 Subject: [PATCH 12/46] Added new SpEL Function to get the owner for all supported CI systems --- .../helper/git/ActionGitSpelFunctions.java | 35 ++++++++++++++++++- .../cli/fod/actions/zip/create-pr.yaml | 6 +++- .../cli/ssc/actions/zip/create-pr.yaml | 6 +++- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java index 99ae6d6ca15..c8dd15a8d8e 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java @@ -271,6 +271,39 @@ public String push( } } + + @SpelFunction(cat=util, desc="Detects the repository owner from CI environment variables. Checks GITHUB_REPOSITORY_OWNER (GitHub), CI_PROJECT_NAMESPACE (GitLab), BUILD_REPOSITORY_ID (Azure DevOps), or BITBUCKET_WORKSPACE (Bitbucket). Returns null if not running in a supported CI system.", + returns="The repository owner/namespace or null if not detectable") + public String ciRepositoryOwner() { + // GitHub Actions + var owner = EnvHelper.env("GITHUB_REPOSITORY_OWNER"); + if (StringUtils.isNotBlank(owner)) { + log.debug("ciRepositoryOwner: Detected from GITHUB_REPOSITORY_OWNER={}", owner); + return owner; + } + // GitLab CI + owner = EnvHelper.env("CI_PROJECT_NAMESPACE"); + if (StringUtils.isNotBlank(owner)) { + log.debug("ciRepositoryOwner: Detected from CI_PROJECT_NAMESPACE={}", owner); + return owner; + } + // Azure DevOps + var buildRepoId = EnvHelper.env("BUILD_REPOSITORY_ID"); + owner = EnvHelper.env("SYSTEM_TEAMPROJECT"); + if (StringUtils.isNotBlank(buildRepoId) && StringUtils.isNotBlank(owner)) { + log.debug("ciRepositoryOwner: Detected from Azure DevOps SYSTEM_TEAMPROJECT={}", owner); + return owner; + } + // Bitbucket Pipelines + owner = EnvHelper.env("BITBUCKET_WORKSPACE"); + if (StringUtils.isNotBlank(owner)) { + log.debug("ciRepositoryOwner: Detected from BITBUCKET_WORKSPACE={}", owner); + return owner; + } + log.debug("ciRepositoryOwner: No CI environment detected, returning null"); + return null; + } + @SpelFunction(cat=util, desc="Detects the default branch of the remote repository. Checks CI environment variables (CI_DEFAULT_BRANCH for GitLab, looks up via GitHub API env), then falls back to reading refs/remotes/origin/HEAD from the local git config. Returns null if detection fails.", returns="The default branch name (e.g. 'main', 'master', 'develop') or null if not detectable") public String defaultBranch( @@ -374,4 +407,4 @@ private static String[] deriveRepoNames(String fallbackShort, String remoteUrl) return new String[]{fallbackShort, null}; } } -} +} \ No newline at end of file diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml index 70633604d9c..44a72064132 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml @@ -41,6 +41,10 @@ steps: # Log initial git repository information for diagnostics - var.set: gitRepoInfo: ${#git.localRepo(sourceDir)} + - if: ${gitRepoInfo==null} + throw: "Source directory '${sourceDir}' is not a git repository. Cannot detect repository information needed for PR creation." + - if: ${#isBlank(gitRepoInfo.repository.remoteUrl)} + throw: "Git repository has no remote URL configured. Cannot create PR without remote repository access." - if: ${gitRepoInfo!=null} log.info: "Git repository: workspace=${gitRepoInfo.repository.workspaceDir}, remote=${gitRepoInfo.repository.remoteUrl?:'not configured'}, branch=${gitRepoInfo.branch.short_?:'detached HEAD'}" @@ -140,7 +144,7 @@ steps: - if: ${ci.type=='github'} do: - var.set: - repoOwner: "${#gitRepoInfo.repository.remoteUrl.replaceAll('.*github\\.com[:/]([^/]+)/.*', '$1')}" + repoOwner: "${#git.ciRepositoryOwner()}" prHead: "${repoOwner}:${branchName}" - log.debug: "Creating GitHub PR: head=${prHead}, base=${baseBranch}, title=${prTitle}" - var.set: diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml index d6de2206186..f12a43840b0 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml @@ -41,6 +41,10 @@ steps: # Log initial git repository information for diagnostics - var.set: gitRepoInfo: ${#git.localRepo(sourceDir)} + - if: ${gitRepoInfo==null} + throw: "Source directory '${sourceDir}' is not a git repository. Cannot detect repository information needed for PR creation." + - if: ${#isBlank(gitRepoInfo.repository.remoteUrl)} + throw: "Git repository has no remote URL configured. Cannot create PR without remote repository access." - if: ${gitRepoInfo!=null} log.info: "Git repository: workspace=${gitRepoInfo.repository.workspaceDir}, remote=${gitRepoInfo.repository.remoteUrl?:'not configured'}, branch=${gitRepoInfo.branch.short_?:'detached HEAD'}" @@ -140,7 +144,7 @@ steps: - if: ${ci.type=='github'} do: - var.set: - repoOwner: "${#gitRepoInfo.repository.remoteUrl.replaceAll('.*github\\.com[:/]([^/]+)/.*', '$1')}" + repoOwner: "${#git.ciRepositoryOwner()}" prHead: "${repoOwner}:${branchName}" - log.debug: "Creating GitHub PR: head=${prHead}, base=${baseBranch}, title=${prTitle}" - var.set: From 7a2038a83999334d7c22795300f38dc83251b7cc Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Mon, 15 Jun 2026 14:25:58 +0530 Subject: [PATCH 13/46] fix broken flow for the code push --- .../helper/git/ActionGitSpelFunctions.java | 47 ++++++++++++------- .../cli/fod/actions/zip/create-pr.yaml | 2 +- .../cli/ssc/actions/zip/create-pr.yaml | 2 +- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java index c8dd15a8d8e..47fd6a1ab92 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java @@ -14,7 +14,6 @@ import static com.fortify.cli.common.spel.fn.descriptor.annotation.SpelFunction.SpelFunctionCategory.util; -import java.io.IOException; import java.nio.file.Path; import org.apache.commons.lang3.StringUtils; @@ -23,6 +22,8 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -242,36 +243,48 @@ public String commit( } @SpelFunction(cat=util, desc="Pushes the current branch to the remote repository. Uses token-based authentication from CI environment variables (GITHUB_TOKEN, CI_JOB_TOKEN, SYSTEM_ACCESSTOKEN, BITBUCKET_TOKEN) if available.", - returns="The name of the remote ref that was pushed") + returns="The name of the remote ref that was pushed") public String push( - @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir) { + @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir, + @SpelFunctionParam(name="branchName", desc="name of the branch to push") String branchName) { try (var git = openGit(sourceDir)) { if (git == null) { throw new FcliSimpleException("Not a git repository: " + sourceDir); } + var credentialsProvider = detectCredentialsProvider(); - if (credentialsProvider == null) { - var remoteUrl = git.getRepository().getConfig().getString("remote", "origin", "url"); - if (remoteUrl != null && remoteUrl.startsWith("https")) { - throw new FcliSimpleException("No credentials available for push to " + remoteUrl - + ". Set one of: GITHUB_TOKEN, GH_TOKEN, CI_JOB_TOKEN, SYSTEM_ACCESSTOKEN, BITBUCKET_TOKEN"); - } - } - var pushCommand = git.push(); + + var pushCommand = git.push() + .setRemote("origin") + .setRefSpecs(new RefSpec("HEAD:refs/heads/" + branchName)); + if (credentialsProvider != null) { pushCommand.setCredentialsProvider(credentialsProvider); } + var results = pushCommand.call(); - log.debug("push: Successfully pushed branch={} to remote", results); - var ref = git.getRepository().getFullBranch(); - log.info("Pushed branch to remote: {}", ref); - return ref; - } catch (GitAPIException | IOException e) { + + // ✅ Validate push result + for (var result : results) { + for (var update : result.getRemoteUpdates()) { + var status = update.getStatus(); + if (status != RemoteRefUpdate.Status.OK && + status != RemoteRefUpdate.Status.UP_TO_DATE) { + throw new FcliSimpleException( + "Push failed for " + update.getRemoteName() + ": " + status + ); + } + } + } + + log.info("Successfully pushed branch to remote: {}", branchName); + return "refs/heads/" + branchName; + + } catch (GitAPIException e) { throw new FcliSimpleException("Failed to push: " + e.getMessage()); } } - @SpelFunction(cat=util, desc="Detects the repository owner from CI environment variables. Checks GITHUB_REPOSITORY_OWNER (GitHub), CI_PROJECT_NAMESPACE (GitLab), BUILD_REPOSITORY_ID (Azure DevOps), or BITBUCKET_WORKSPACE (Bitbucket). Returns null if not running in a supported CI system.", returns="The repository owner/namespace or null if not detectable") public String ciRepositoryOwner() { diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml index 44a72064132..75580b74d59 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml @@ -113,7 +113,7 @@ steps: # Push branch to remote with detailed error context - var.set: - pushedRef: ${#git.push(sourceDir)} + pushedRef: ${#git.push(sourceDir, branchName)} on.fail: - var.set: errorMsg: "Failed to push branch '${branchName}' to remote" diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml index f12a43840b0..970d117bc81 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml @@ -113,7 +113,7 @@ steps: # Push branch to remote with detailed error context - var.set: - pushedRef: ${#git.push(sourceDir)} + pushedRef: ${#git.push(sourceDir, branchName)} on.fail: - var.set: errorMsg: "Failed to push branch '${branchName}' to remote" From 0f9a4f4a5284aea93817b70bdebb07ea1e56a5c8 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Mon, 15 Jun 2026 15:58:33 +0530 Subject: [PATCH 14/46] Improv Git SpEL Functions --- .../helper/git/ActionGitSpelFunctions.java | 151 ++++++++++++++---- .../cli/fod/actions/zip/create-pr.yaml | 2 +- .../cli/ssc/actions/zip/create-pr.yaml | 2 +- 3 files changed, 118 insertions(+), 37 deletions(-) diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java index 47fd6a1ab92..f37efb42b56 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java @@ -14,6 +14,7 @@ import static com.fortify.cli.common.spel.fn.descriptor.annotation.SpelFunction.SpelFunctionCategory.util; +import java.io.IOException; import java.nio.file.Path; import org.apache.commons.lang3.StringUtils; @@ -23,7 +24,6 @@ import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.RefSpec; -import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -86,7 +86,7 @@ public ObjectNode localRepo( var mapper = JsonHelper.getObjectMapper(); var remote = selectRemote(repo); - var remoteUrl = remote == null ? null : repo.getConfig().getString("remote", remote, "url"); + var remoteUrl = remote == null ? "origin" : repo.getConfig().getString("remote", remote, "url"); var names = deriveRepoNames(dir.getName(), remoteUrl); var repository = CiRepository.builder() .workspaceDir(repo.getWorkTree().getAbsolutePath()) @@ -159,7 +159,10 @@ public ObjectNode localRepo( root.set("commit", mapper.valueToTree(commit)); } return root; - } catch (Exception e) { return null; } + } catch (Exception e) { + log.debug("localRepo failed for {}", sourceDir, e); + return null; + } } @SpelFunction(cat=util, desc="Checks whether the working tree of the git repository at the given directory has any uncommitted changes (modified, added, or deleted files).", @@ -170,13 +173,15 @@ public boolean hasChanges( if (git == null) { return false; } log.debug("hasChanges: Checking for uncommitted changes in sourceDir={}", sourceDir); var status = git.status().call(); - return !status.getModified().isEmpty() + boolean hasChanges = !status.getModified().isEmpty() || !status.getAdded().isEmpty() || !status.getRemoved().isEmpty() || !status.getUntracked().isEmpty() || !status.getChanged().isEmpty(); + log.debug("hasChanges: {} → {}", sourceDir, hasChanges); + return hasChanges; } catch (Exception e) { - log.debug("Error checking git status", e); + log.debug("hasChanges error for {}", sourceDir, e); return false; } } @@ -193,11 +198,18 @@ public String createBranch( var timestamp = java.time.LocalDateTime.now() .format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")); var branchName = branchPrefix + "/" + timestamp; - git.checkout().setCreateBranch(true).setName(branchName).call(); - log.debug("createBranch: Created branch={}", branchName); + git.checkout() + .setCreateBranch(true) + .setName(branchName) + .call(); + String current = git.getRepository().getBranch(); + if (!branchName.equals(current)) { + throw new FcliSimpleException("Failed to checkout branch " + branchName); + } + log.info("Created and checked out branch: {}", branchName); return branchName; - } catch (GitAPIException e) { + } catch (GitAPIException | IOException e) { throw new FcliSimpleException("Failed to create branch: " + e.getMessage()); } } @@ -210,9 +222,12 @@ public boolean addAll( if (git == null) { throw new FcliSimpleException("Not a git repository: " + sourceDir); } + //stage new files git.add().addFilepattern(".").call(); + + //stage modified files (git add with update=true will stage modifications and deletions, but not new untracked files, which is why we call add twice) git.add().setUpdate(true).addFilepattern(".").call(); - log.debug("addAll: Staged all changes in sourceDir={}", sourceDir); + log.info("Staged all changes in: {}", sourceDir); return true; } catch (GitAPIException e) { @@ -229,12 +244,20 @@ public String commit( if (git == null) { throw new FcliSimpleException("Not a git repository: " + sourceDir); } + + if (git.status().call().isClean()) { + throw new FcliSimpleException("No changes to commit"); + } + + String name = "fcli-actions[bot]"; //TODO - Check if we can get author info from env or git config + String email = "fcli-actions@opentext.com"; //TODO - Check if we can get author info from env or git config + var commitResult = git.commit() .setMessage(message) - .setAuthor("fcli", "fcli@fortify.com") + .setAuthor(name, email) + .setCommitter(name, email) .call(); var sha = commitResult.getId().getName(); - log.debug("commit: Committed message={}, sha={}", message, sha); log.info("Committed changes: {}", sha); return sha; } catch (GitAPIException e) { @@ -252,36 +275,94 @@ public String push( throw new FcliSimpleException("Not a git repository: " + sourceDir); } + var repo = git.getRepository(); + var remote = selectRemote(repo); + if (remote == null) remote = "origin"; + + var remoteUrl = repo.getConfig().getString("remote", remote, "url"); + + log.info("PUSH DEBUG: remote={}", remote); + log.info("PUSH DEBUG: remoteUrl={}", remoteUrl); + log.info("PUSH DEBUG: branchName={}", branchName); + var credentialsProvider = detectCredentialsProvider(); + if (credentialsProvider == null) { + log.warn("PUSH DEBUG: No credentials provider detected"); + } else { + log.debug("PUSH DEBUG: Using credentials provider={}", credentialsProvider.getClass().getName()); + } + + String refSpecStr = "HEAD:refs/heads/" + branchName; + var refSpec = new RefSpec(refSpecStr); - var pushCommand = git.push() - .setRemote("origin") - .setRefSpecs(new RefSpec("HEAD:refs/heads/" + branchName)); + log.info("PUSH DEBUG: refSpec={}", refSpecStr); + + var pushCmd = git.push() + .setRemote(remote) + .setRefSpecs(refSpec) + .setPushAll() // Safe in most CI cases; optional + .setTimeout(60); if (credentialsProvider != null) { - pushCommand.setCredentialsProvider(credentialsProvider); + pushCmd.setCredentialsProvider(credentialsProvider); } - var results = pushCommand.call(); + var results = pushCmd.call(); + + boolean success = false; - // ✅ Validate push result for (var result : results) { + log.info("PUSH DEBUG: --- PushResult Start ---"); + + var messages = result.getMessages(); + if (!StringUtils.isBlank(messages)) { + log.warn("PUSH DEBUG: Remote messages:\n{}", messages); + } + for (var update : result.getRemoteUpdates()) { var status = update.getStatus(); - if (status != RemoteRefUpdate.Status.OK && - status != RemoteRefUpdate.Status.UP_TO_DATE) { - throw new FcliSimpleException( - "Push failed for " + update.getRemoteName() + ": " + status - ); + + log.info("PUSH DEBUG: Update remoteName={}", update.getRemoteName()); + log.info("PUSH DEBUG: Update status={}", status); + log.info("PUSH DEBUG: Update srcRef={}", update.getSrcRef()); + log.info("PUSH DEBUG: Update dstRef={}", update.getRemoteName()); + log.info("PUSH DEBUG: Update message={}", update.getMessage()); + + switch (status) { + case OK: + case UP_TO_DATE: + success = true; + break; + + case REJECTED_NONFASTFORWARD: + case REJECTED_NODELETE: + case REJECTED_REMOTE_CHANGED: + case REJECTED_OTHER_REASON: + case NON_EXISTING: + case NOT_ATTEMPTED: + default: + throw new FcliSimpleException( + "Push rejected: " + + "status=" + status + + ", remote=" + update.getRemoteName() + + ", message=" + update.getMessage() + ); } } + + log.info("PUSH DEBUG: --- PushResult End ---"); + } + + if (!success) { + throw new FcliSimpleException("Push completed but no refs were updated (likely auth or refspec issue)"); } - log.info("Successfully pushed branch to remote: {}", branchName); + log.info("Successfully pushed branch: {}", branchName); return "refs/heads/" + branchName; - } catch (GitAPIException e) { - throw new FcliSimpleException("Failed to push: " + e.getMessage()); + } catch (Exception e) { + log.error("PUSH DEBUG: Exception occurred during push", e); + throw new FcliSimpleException("Failed to push: " + e.getMessage(), e); } } @@ -350,13 +431,12 @@ public String defaultBranch( private Git openGit(String sourceDir) { if (StringUtils.isBlank(sourceDir)) { return null; } - var dir = Path.of(sourceDir).toAbsolutePath().normalize().toFile(); - if (!dir.exists()) { return null; } - var builder = new FileRepositoryBuilder().findGitDir(dir); - if (builder.getGitDir() == null) { return null; } try { - var repo = builder.build(); - return new Git(repo); + var dir = Path.of(sourceDir).toAbsolutePath().normalize().toFile(); + if (!dir.exists()) { return null; } + var builder = new FileRepositoryBuilder().findGitDir(dir); + if (builder.getGitDir() == null) { return null; } + return new Git(builder.build()); } catch (Exception e) { return null; } @@ -390,10 +470,11 @@ private CredentialsProvider detectCredentialsProvider() { private static String selectRemote(Repository repo) { try { var remotes = repo.getRemoteNames(); - if (remotes == null || remotes.isEmpty()) { return null; } - if (remotes.contains("origin")) { return "origin"; } - return remotes.iterator().next(); - } catch (Exception e) { return null; } + if (remotes == null || remotes.isEmpty()) return null; + return remotes.contains("origin") ? "origin" : remotes.iterator().next(); + } catch (Exception e) { + return null; + } } private static String[] deriveRepoNames(String fallbackShort, String remoteUrl) { diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml index 75580b74d59..8e2a714eab6 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml @@ -65,7 +65,7 @@ steps: - var.set: branchPrefix: ${#env('PR_BRANCH_PREFIX')?:'aviator/remediations'} - commitMessage: "${#env('PR_COMMIT_MESSAGE')?:'fix: apply Fortify auto-remediation fixes'}" + commitMessage: "${#env('PR_COMMIT_MESSAGE')?:'fix: apply Fortify auto-remediation fixes [generated by fcli]'}" prTitle: "${#env('PR_TITLE')?:'fix: Fortify auto-remediation fixes'}" prBody: "${#env('PR_BODY')?:'This pull request contains auto-remediation fixes generated by Fortify SAST Aviator.'}" diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml index 970d117bc81..e5321eef16a 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml @@ -65,7 +65,7 @@ steps: - var.set: branchPrefix: ${#env('PR_BRANCH_PREFIX')?:'aviator/remediations'} - commitMessage: "${#env('PR_COMMIT_MESSAGE')?:'fix: apply Fortify auto-remediation fixes'}" + commitMessage: "${#env('PR_COMMIT_MESSAGE')?:'fix: apply Fortify auto-remediation fixes [generated by fcli]'}" prTitle: "${#env('PR_TITLE')?:'fix: Fortify auto-remediation fixes'}" prBody: "${#env('PR_BODY')?:'This pull request contains auto-remediation fixes generated by Fortify SAST Aviator.'}" From b6fc80517dd1b0eb5b58133fce87e4fddd620d78 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Mon, 15 Jun 2026 18:25:42 +0530 Subject: [PATCH 15/46] set upstream tracking --- .../helper/git/ActionGitSpelFunctions.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java index f37efb42b56..f190b6c79e7 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java @@ -292,15 +292,16 @@ public String push( log.debug("PUSH DEBUG: Using credentials provider={}", credentialsProvider.getClass().getName()); } - String refSpecStr = "HEAD:refs/heads/" + branchName; - var refSpec = new RefSpec(refSpecStr); + String fullBranchRef = "refs/heads/" + branchName; + var refSpec = new RefSpec(fullBranchRef + ":" + fullBranchRef); - log.info("PUSH DEBUG: refSpec={}", refSpecStr); + log.info("PUSH DEBUG: refSpec={}", fullBranchRef); + git.fetch().setRemote(remote).call(); + var pushCmd = git.push() .setRemote(remote) .setRefSpecs(refSpec) - .setPushAll() // Safe in most CI cases; optional .setTimeout(60); if (credentialsProvider != null) { @@ -309,6 +310,13 @@ public String push( var results = pushCmd.call(); + + // Set upstream tracking + StoredConfig config = git.getRepository().getConfig(); + config.setString("branch", branchName, "remote", remote); + config.setString("branch", branchName, "merge", fullBranchRef); + config.save(); + boolean success = false; for (var result : results) { From 911b8d0c221f39bf5187a888dcb45cdf574bad92 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Mon, 15 Jun 2026 18:35:20 +0530 Subject: [PATCH 16/46] fix java package import --- .../cli/common/action/helper/git/ActionGitSpelFunctions.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java index f190b6c79e7..fbf54d0391d 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java @@ -21,6 +21,7 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.RefSpec; @@ -298,7 +299,7 @@ public String push( log.info("PUSH DEBUG: refSpec={}", fullBranchRef); git.fetch().setRemote(remote).call(); - + var pushCmd = git.push() .setRemote(remote) .setRefSpecs(refSpec) From f57f264ca8045a1c60144b92953db43964d2c36a Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Mon, 15 Jun 2026 20:01:01 +0530 Subject: [PATCH 17/46] Add more logs to the push SpEL Function --- .../helper/git/ActionGitSpelFunctions.java | 82 +++++++++++++++---- 1 file changed, 66 insertions(+), 16 deletions(-) diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java index fbf54d0391d..4b013d88cf7 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java @@ -265,41 +265,81 @@ public String commit( throw new FcliSimpleException("Failed to commit: " + e.getMessage()); } } - - @SpelFunction(cat=util, desc="Pushes the current branch to the remote repository. Uses token-based authentication from CI environment variables (GITHUB_TOKEN, CI_JOB_TOKEN, SYSTEM_ACCESSTOKEN, BITBUCKET_TOKEN) if available.", - returns="The name of the remote ref that was pushed") + + @SpelFunction( + cat = util, + desc = "Pushes the current branch to the remote repository. Uses token-based authentication from CI environment variables (GITHUB_TOKEN, CI_JOB_TOKEN, SYSTEM_ACCESSTOKEN, BITBUCKET_TOKEN) if available.", + returns = "The name of the remote ref that was pushed" + ) public String push( - @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir, - @SpelFunctionParam(name="branchName", desc="name of the branch to push") String branchName) { + @SpelFunctionParam(name = "sourceDir", desc = "directory inside a git working tree") String sourceDir, + @SpelFunctionParam(name = "branchName", desc = "name of the branch to push") String branchName) { try (var git = openGit(sourceDir)) { if (git == null) { throw new FcliSimpleException("Not a git repository: " + sourceDir); } var repo = git.getRepository(); + + // ✅ Select remote var remote = selectRemote(repo); if (remote == null) remote = "origin"; + // ✅ Ensure branch checkout (handles detached HEAD) + try { + git.checkout().setName(branchName).call(); + } catch (Exception e) { + // fallback: recreate branch + git.checkout() + .setCreateBranch(true) + .setName(branchName) + .setStartPoint("HEAD") + .call(); + } + + // ✅ Get and fix remote URL var remoteUrl = repo.getConfig().getString("remote", remote, "url"); + if (remoteUrl != null && !remoteUrl.endsWith(".git")) { + remoteUrl = remoteUrl + ".git"; + repo.getConfig().setString("remote", remote, "url", remoteUrl); + repo.getConfig().save(); + } log.info("PUSH DEBUG: remote={}", remote); log.info("PUSH DEBUG: remoteUrl={}", remoteUrl); log.info("PUSH DEBUG: branchName={}", branchName); - var credentialsProvider = detectCredentialsProvider(); + // ✅ Detect credentials + CredentialsProvider credentialsProvider = detectCredentialsProvider(); + // ✅ Force GitHub token explicitly (stronger than relying only on helper) + var token = System.getenv("GITHUB_TOKEN"); + log.info("PUSH DEBUG: GITHUB_TOKEN present={}", token != null); + + if (token != null) { + log.info("PUSH DEBUG: Overriding credentials with explicit GITHUB_TOKEN"); + credentialsProvider = new UsernamePasswordCredentialsProvider("x-access-token", token); + } + if (credentialsProvider == null) { - log.warn("PUSH DEBUG: No credentials provider detected"); + log.warn("PUSH DEBUG: No credentials provider detected - push will likely fail"); } else { - log.debug("PUSH DEBUG: Using credentials provider={}", credentialsProvider.getClass().getName()); + log.info("PUSH DEBUG: Using credentials provider={}", credentialsProvider.getClass().getName()); } + // ✅ Prepare refspec String fullBranchRef = "refs/heads/" + branchName; var refSpec = new RefSpec(fullBranchRef + ":" + fullBranchRef); log.info("PUSH DEBUG: refSpec={}", fullBranchRef); - git.fetch().setRemote(remote).call(); + // ✅ Fetch with credentials (important for CI consistency) + var fetchCmd = git.fetch().setRemote(remote); + if (credentialsProvider != null) { + fetchCmd.setCredentialsProvider(credentialsProvider); + } + fetchCmd.call(); + // ✅ Push (NO pushAll) var pushCmd = git.push() .setRemote(remote) .setRefSpecs(refSpec) @@ -311,9 +351,8 @@ public String push( var results = pushCmd.call(); - - // Set upstream tracking - StoredConfig config = git.getRepository().getConfig(); + // ✅ Set upstream AFTER successful push + StoredConfig config = repo.getConfig(); config.setString("branch", branchName, "remote", remote); config.setString("branch", branchName, "merge", fullBranchRef); config.save(); @@ -363,15 +402,26 @@ public String push( } if (!success) { - throw new FcliSimpleException("Push completed but no refs were updated (likely auth or refspec issue)"); + throw new FcliSimpleException("Push completed but no refs were updated (likely auth or permission issue)"); } log.info("Successfully pushed branch: {}", branchName); - return "refs/heads/" + branchName; + return fullBranchRef; } catch (Exception e) { - log.error("PUSH DEBUG: Exception occurred during push", e); - throw new FcliSimpleException("Failed to push: " + e.getMessage(), e); + // ✅ Deep root cause extraction + Throwable root = e; + while (root.getCause() != null) { + root = root.getCause(); + } + + log.error("PUSH DEBUG: Root cause type={}", root.getClass().getName()); + log.error("PUSH DEBUG: Root cause message={}", root.getMessage(), root); + + throw new FcliSimpleException( + "Failed to push (root cause): " + root.getClass().getName() + " - " + root.getMessage(), + e + ); } } From 37929da5782043945f2cd0f6f90d8da5dd1b4586 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Mon, 15 Jun 2026 22:05:20 +0530 Subject: [PATCH 18/46] fix(git): resolve JGit runtime issue by excluding from shadowJar and add debug logging --- fcli-core/fcli-app/build.gradle.kts | 3 +++ .../action/helper/git/ActionGitSpelFunctions.java | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/fcli-core/fcli-app/build.gradle.kts b/fcli-core/fcli-app/build.gradle.kts index 12c38c2a221..91d1b017943 100644 --- a/fcli-core/fcli-app/build.gradle.kts +++ b/fcli-core/fcli-app/build.gradle.kts @@ -100,6 +100,9 @@ tasks.named("shadowJ archiveVersion.set("") from(generatedReflectConfigDir) exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA") + dependencies { + exclude(dependency("org.eclipse.jgit:org.eclipse.jgit")) + } } // Third-party helper diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java index 4b013d88cf7..c64fa90675b 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java @@ -274,6 +274,19 @@ public String commit( public String push( @SpelFunctionParam(name = "sourceDir", desc = "directory inside a git working tree") String sourceDir, @SpelFunctionParam(name = "branchName", desc = "name of the branch to push") String branchName) { + + log.info("PUSH DEBUG: JGit version = {}", + org.eclipse.jgit.lib.Constants.class + .getPackage() + .getImplementationVersion()); + + + log.info("PUSH DEBUG: JGit loaded from = {}", + org.eclipse.jgit.lib.GcConfig.class + .getProtectionDomain() + .getCodeSource() + .getLocation()); + try (var git = openGit(sourceDir)) { if (git == null) { throw new FcliSimpleException("Not a git repository: " + sourceDir); From 1c417309cb315ac25dbccd21d21d9025981762ec Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Mon, 15 Jun 2026 22:51:37 +0530 Subject: [PATCH 19/46] Remove including specific version of jgit --- fcli-core/fcli-app/build.gradle.kts | 3 --- 1 file changed, 3 deletions(-) diff --git a/fcli-core/fcli-app/build.gradle.kts b/fcli-core/fcli-app/build.gradle.kts index 91d1b017943..12c38c2a221 100644 --- a/fcli-core/fcli-app/build.gradle.kts +++ b/fcli-core/fcli-app/build.gradle.kts @@ -100,9 +100,6 @@ tasks.named("shadowJ archiveVersion.set("") from(generatedReflectConfigDir) exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA") - dependencies { - exclude(dependency("org.eclipse.jgit:org.eclipse.jgit")) - } } // Third-party helper From 99221f811a97e2bb1546a6b57217e9048f66d57e Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Tue, 16 Jun 2026 10:21:30 +0530 Subject: [PATCH 20/46] Added GcConfig enum entries --- .../fcli/fcli-app/jgit/reflect-config.json | 586 ++++++++++++------ 1 file changed, 405 insertions(+), 181 deletions(-) diff --git a/fcli-core/fcli-app/src/main/resources/META-INF/native-image/fcli/fcli-app/jgit/reflect-config.json b/fcli-core/fcli-app/src/main/resources/META-INF/native-image/fcli/fcli-app/jgit/reflect-config.json index 15a37b3ea4d..f055ce35015 100644 --- a/fcli-core/fcli-app/src/main/resources/META-INF/native-image/fcli/fcli-app/jgit/reflect-config.json +++ b/fcli-core/fcli-app/src/main/resources/META-INF/native-image/fcli/fcli-app/jgit/reflect-config.json @@ -7,9 +7,7 @@ "methods": [ { "name": "", - "parameterTypes": [ - - ] + "parameterTypes": [] } ] }, @@ -28,9 +26,7 @@ "methods": [ { "name": "values", - "parameterTypes": [ - - ] + "parameterTypes": [] } ] }, @@ -42,9 +38,7 @@ "methods": [ { "name": "values", - "parameterTypes": [ - - ] + "parameterTypes": [] } ] }, @@ -56,9 +50,7 @@ "methods": [ { "name": "values", - "parameterTypes": [ - - ] + "parameterTypes": [] } ] }, @@ -70,9 +62,7 @@ "methods": [ { "name": "values", - "parameterTypes": [ - - ] + "parameterTypes": [] } ] }, @@ -84,9 +74,7 @@ "methods": [ { "name": "values", - "parameterTypes": [ - - ] + "parameterTypes": [] } ] }, @@ -98,9 +86,7 @@ "methods": [ { "name": "values", - "parameterTypes": [ - - ] + "parameterTypes": [] } ] }, @@ -112,9 +98,7 @@ "methods": [ { "name": "values", - "parameterTypes": [ - - ] + "parameterTypes": [] } ] }, @@ -126,9 +110,7 @@ "methods": [ { "name": "values", - "parameterTypes": [ - - ] + "parameterTypes": [] } ] }, @@ -140,9 +122,7 @@ "methods": [ { "name": "values", - "parameterTypes": [ - - ] + "parameterTypes": [] } ] }, @@ -154,248 +134,492 @@ "methods": [ { "name": "values", + "parameterTypes": [] + } + ] + }, + { + "name": "com.github.chirontt.gitserver.LfsBatchServlet", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "methods": [ + { + "name": "", "parameterTypes": [ - + "org.eclipse.jgit.lfs.server.fs.FileLfsRepository", + "java.nio.file.Path" ] } ] }, { - "name":"com.github.chirontt.gitserver.LfsBatchServlet", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "allPublicMethods":true, - "methods":[{"name":"","parameterTypes":["org.eclipse.jgit.lfs.server.fs.FileLfsRepository","java.nio.file.Path"] }] + "name": "com.github.chirontt.lfs.server.LfsProtocolServletV2$LfsRequestV2", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { - "name":"com.github.chirontt.lfs.server.LfsProtocolServletV2$LfsRequestV2", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "com.github.chirontt.lfs.server.LfsRef", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { - "name":"com.github.chirontt.lfs.server.LfsRef", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "com.github.chirontt.lfs.server.locks.LfsFileLockingRequest$CreateLock", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { - "name":"com.github.chirontt.lfs.server.locks.LfsFileLockingRequest$CreateLock", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "com.github.chirontt.lfs.server.locks.LfsFileLockingRequest$DeleteLock", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { - "name":"com.github.chirontt.lfs.server.locks.LfsFileLockingRequest$DeleteLock", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "com.github.chirontt.lfs.server.locks.LfsFileLockingRequest$ListLocksToVerify", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { - "name":"com.github.chirontt.lfs.server.locks.LfsFileLockingRequest$ListLocksToVerify", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$CreatedOrDeletedLock", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "", + "parameterTypes": [ + "com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$Lock" + ] + } + ] }, { - "name":"com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$CreatedOrDeletedLock", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"","parameterTypes":["com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$Lock"] } + "name": "com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$Error", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "name": "com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$Lock", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$LockExistsError", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.String", + "com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$Lock" + ] + } + ] + }, + { + "name": "com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$Locks", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "", + "parameterTypes": [ + "java.util.List", + "java.lang.String" + ] + } ] }, { - "name":"com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$Error", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[{"name":"","parameterTypes":["java.lang.String"] }] + "name": "com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$LocksToVerify", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$Owner", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "", + "parameterTypes": [ + "java.lang.String" + ] + } + ] }, { - "name":"com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$Lock", - "allDeclaredFields":true, - "allDeclaredMethods":true + "name": "com.github.chirontt.lfs.server.locks.lm.PersistentLock", + "allDeclaredFields": true, + "allDeclaredMethods": true }, { - "name":"com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$LockExistsError", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[{"name":"","parameterTypes":["java.lang.String","com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$Lock"] }] + "name": "com.github.chirontt.lfs.server.locks.internal.LfsFileLockingText", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { - "name":"com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$Locks", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"","parameterTypes":["java.util.List","java.lang.String"] } + "name": "org.eclipse.jgit.diff.DiffAlgorithm$SupportedAlgorithm", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } ] }, { - "name":"com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$LocksToVerify", - "allDeclaredFields":true, - "allDeclaredMethods":true + "name": "org.eclipse.jgit.dircache.DirCache$DirCacheVersion", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"com.github.chirontt.lfs.server.locks.LfsFileLockingResponse$Owner", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[ - {"name":"","parameterTypes":[] }, - {"name":"","parameterTypes":["java.lang.String"] } + "name": "org.eclipse.jgit.gitrepo.internal.RepoText", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } ] }, { - "name":"com.github.chirontt.lfs.server.locks.lm.PersistentLock", - "allDeclaredFields":true, - "allDeclaredMethods":true + "name": "org.eclipse.jgit.http.server.GitServlet", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { - "name":"com.github.chirontt.lfs.server.locks.internal.LfsFileLockingText", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "org.eclipse.jgit.http.server.HttpServerText", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.diff.DiffAlgorithm$SupportedAlgorithm", - "methods":[{"name":"values","parameterTypes":[] }] + "name": "org.eclipse.jgit.internal.JGitText", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.dircache.DirCache$DirCacheVersion", - "methods":[{"name":"values","parameterTypes":[] }] + "name": "org.eclipse.jgit.internal.storage.dfs.DfsText", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.gitrepo.internal.RepoText", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "org.eclipse.jgit.lfs.internal.LfsText", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.http.server.GitServlet", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "allPublicMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "org.eclipse.jgit.lfs.server.LfsObject", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.http.server.HttpServerText", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "org.eclipse.jgit.lfs.server.LfsProtocolServlet$LfsRequest", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.internal.JGitText", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "org.eclipse.jgit.lfs.server.Response$Action", + "allDeclaredFields": true }, { - "name":"org.eclipse.jgit.internal.storage.dfs.DfsText", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "org.eclipse.jgit.lfs.server.Response$Body", + "allDeclaredFields": true }, { - "name":"org.eclipse.jgit.lfs.internal.LfsText", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "org.eclipse.jgit.lfs.server.Response$Error", + "allDeclaredFields": true }, { - "name":"org.eclipse.jgit.lfs.server.LfsObject", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "allPublicMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "org.eclipse.jgit.lfs.server.Response$ObjectInfo", + "allDeclaredFields": true }, { - "name":"org.eclipse.jgit.lfs.server.LfsProtocolServlet$LfsRequest", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "allPublicMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "org.eclipse.jgit.lfs.server.fs.FileLfsServlet", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.eclipse.jgit.lfs.server.fs.FileLfsRepository", + "long" + ] + } + ] }, { - "name":"org.eclipse.jgit.lfs.server.Response$Action", - "allDeclaredFields":true + "name": "org.eclipse.jgit.lfs.server.internal.LfsServerText", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.lfs.server.Response$Body", - "allDeclaredFields":true + "name": "org.eclipse.jgit.lib.CoreConfig$AutoCRLF", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.lfs.server.Response$Error", - "allDeclaredFields":true + "name": "org.eclipse.jgit.lib.CoreConfig$CheckStat", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.lfs.server.Response$ObjectInfo", - "allDeclaredFields":true + "name": "org.eclipse.jgit.lib.CoreConfig$EOL", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.lfs.server.fs.FileLfsServlet", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "allPublicMethods":true, - "methods":[{"name":"","parameterTypes":["org.eclipse.jgit.lfs.server.fs.FileLfsRepository","long"] }] + "name": "org.eclipse.jgit.lib.CoreConfig$HideDotFiles", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.lfs.server.internal.LfsServerText", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "org.eclipse.jgit.lib.CoreConfig$LogRefUpdates", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.lib.CoreConfig$AutoCRLF", - "methods":[{"name":"values","parameterTypes":[] }] + "name": "org.eclipse.jgit.lib.CoreConfig$SymLinks", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.lib.CoreConfig$CheckStat", - "methods":[{"name":"values","parameterTypes":[] }] + "name": "org.eclipse.jgit.lib.CoreConfig$TrustLooseRefStat", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.lib.CoreConfig$EOL", - "methods":[{"name":"values","parameterTypes":[] }] + "name": "org.eclipse.jgit.lib.CoreConfig$TrustPackedRefsStat", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.lib.CoreConfig$HideDotFiles", - "methods":[{"name":"values","parameterTypes":[] }] + "name": "org.eclipse.jgit.lib.CoreConfig$TrustStat", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.lib.CoreConfig$LogRefUpdates", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "allPublicMethods":true, - "methods":[{"name":"values","parameterTypes":[] }] + "name": "org.eclipse.jgit.lib.GpgConfig$GpgFormat", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.lib.CoreConfig$SymLinks", - "methods":[{"name":"values","parameterTypes":[] }] + "name": "org.eclipse.jgit.transport.HttpConfig$HttpRedirectMode", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.lib.CoreConfig$TrustLooseRefStat", - "methods":[{"name":"values","parameterTypes":[] }] + "name": "org.eclipse.jgit.transport.http.apache.internal.HttpApacheText", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.lib.CoreConfig$TrustPackedRefsStat", - "methods":[{"name":"values","parameterTypes":[] }] + "name": "org.eclipse.jgit.lib.GcConfig$PackRefsMode", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.lib.CoreConfig$TrustStat", - "methods":[{"name":"values","parameterTypes":[] }] + "name": "org.eclipse.jgit.lib.GcConfig$AggressiveWindow", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.lib.GpgConfig$GpgFormat", - "methods":[{"name":"values","parameterTypes":[] }] + "name": "org.eclipse.jgit.lib.GcConfig$Autodetach", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.transport.HttpConfig$HttpRedirectMode", - "methods":[{"name":"values","parameterTypes":[] }] + "name": "org.eclipse.jgit.lib.GcConfig$Prune", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] }, { - "name":"org.eclipse.jgit.transport.http.apache.internal.HttpApacheText", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "methods":[{"name":"","parameterTypes":[] }] + "name": "org.eclipse.jgit.lib.GcConfig$LogExpire", + "methods": [ + { + "name": "values", + "parameterTypes": [] + } + ] } -] +] \ No newline at end of file From 9c7a5bc7a9ff99eed8e37674d30c676c8128b454 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Tue, 16 Jun 2026 10:24:58 +0530 Subject: [PATCH 21/46] Commit only modified files, no new files should be staged --- .../cli/common/action/helper/git/ActionGitSpelFunctions.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java index c64fa90675b..566e0075cdb 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java @@ -223,8 +223,8 @@ public boolean addAll( if (git == null) { throw new FcliSimpleException("Not a git repository: " + sourceDir); } - //stage new files - git.add().addFilepattern(".").call(); + // //stage new files + // git.add().addFilepattern(".").call(); //stage modified files (git add with update=true will stage modifications and deletions, but not new untracked files, which is why we call add twice) git.add().setUpdate(true).addFilepattern(".").call(); From ba7beb02aa7fc7540f18cfdca1a58193027b0f20 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Wed, 17 Jun 2026 10:49:22 +0530 Subject: [PATCH 22/46] Code clean up --- .../helper/ci/github/ActionGitHubRepo.java | 3 +- .../helper/ci/gitlab/ActionGitLabProject.java | 3 +- .../helper/git/ActionGitSpelFunctions.java | 94 +------------------ .../cli/fod/actions/zip/create-pr.yaml | 4 +- .../cli/ssc/actions/zip/create-pr.yaml | 4 +- 5 files changed, 10 insertions(+), 98 deletions(-) diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/github/ActionGitHubRepo.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/github/ActionGitHubRepo.java index 12a320da932..9fde6dce06a 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/github/ActionGitHubRepo.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/github/ActionGitHubRepo.java @@ -77,7 +77,8 @@ public ObjectNode createPullRequest( @SpelFunctionParam(name="body", desc="pull request description (Markdown supported)") String body) { return repo.createPullRequest(title, head, base, body); } - @SpelFunction(cat=ci, desc="(PREVIEW) Adds a review comment on a specific file and line in the pull request detected from the workflow run. This function is not yet used by any built-in fcli actions; signature and implementation may change in future fcli versions based on new insights as to how to best integrate this functionality into fcli built-in actions.", + + @SpelFunction(cat=ci, desc="(PREVIEW) Adds a review comment on a specific file and line in the pull request detected from the workflow run. This function is not yet used by any built-in fcli actions; signature and implementation may change in future fcli versions based on new insights as to how to best integrate this functionality into fcli built-in actions.", returns="Created review comment object") public ObjectNode addReviewComment( @SpelFunctionParam(name="path", desc="file path relative to repository root") String path, diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/gitlab/ActionGitLabProject.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/gitlab/ActionGitLabProject.java index 3a1b492fd26..2970765ea4b 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/gitlab/ActionGitLabProject.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/gitlab/ActionGitLabProject.java @@ -78,7 +78,8 @@ public ObjectNode createMergeRequest( @SpelFunctionParam(name="description", desc="merge request description (Markdown supported)") String description) { return project.createMergeRequest(title, sourceBranch, targetBranch, description); } - private String requirePipelineId(String operation) { + + private String requirePipelineId(String operation) { var pipelineId = env.pipelineId(); if (StringUtils.isBlank(pipelineId)) { throw new FcliSimpleException( diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java index 566e0075cdb..9d14018283e 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java @@ -83,9 +83,7 @@ public ObjectNode localRepo( FileRepositoryBuilder builder = new FileRepositoryBuilder().findGitDir(dir); if (builder.getGitDir() == null) { return null; } try (Repository repo = builder.build()) { - log.debug("localRepo: Processing sourceDir={}", sourceDir); var mapper = JsonHelper.getObjectMapper(); - var remote = selectRemote(repo); var remoteUrl = remote == null ? "origin" : repo.getConfig().getString("remote", remote, "url"); var names = deriveRepoNames(dir.getName(), remoteUrl); @@ -161,7 +159,6 @@ public ObjectNode localRepo( } return root; } catch (Exception e) { - log.debug("localRepo failed for {}", sourceDir, e); return null; } } @@ -172,17 +169,14 @@ public boolean hasChanges( @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir) { try (var git = openGit(sourceDir)) { if (git == null) { return false; } - log.debug("hasChanges: Checking for uncommitted changes in sourceDir={}", sourceDir); var status = git.status().call(); boolean hasChanges = !status.getModified().isEmpty() || !status.getAdded().isEmpty() || !status.getRemoved().isEmpty() || !status.getUntracked().isEmpty() || !status.getChanged().isEmpty(); - log.debug("hasChanges: {} → {}", sourceDir, hasChanges); return hasChanges; } catch (Exception e) { - log.debug("hasChanges error for {}", sourceDir, e); return false; } } @@ -207,8 +201,6 @@ public String createBranch( if (!branchName.equals(current)) { throw new FcliSimpleException("Failed to checkout branch " + branchName); } - - log.info("Created and checked out branch: {}", branchName); return branchName; } catch (GitAPIException | IOException e) { throw new FcliSimpleException("Failed to create branch: " + e.getMessage()); @@ -223,13 +215,7 @@ public boolean addAll( if (git == null) { throw new FcliSimpleException("Not a git repository: " + sourceDir); } - // //stage new files - // git.add().addFilepattern(".").call(); - - //stage modified files (git add with update=true will stage modifications and deletions, but not new untracked files, which is why we call add twice) git.add().setUpdate(true).addFilepattern(".").call(); - - log.info("Staged all changes in: {}", sourceDir); return true; } catch (GitAPIException e) { throw new FcliSimpleException("Failed to stage files: " + e.getMessage()); @@ -259,7 +245,6 @@ public String commit( .setCommitter(name, email) .call(); var sha = commitResult.getId().getName(); - log.info("Committed changes: {}", sha); return sha; } catch (GitAPIException e) { throw new FcliSimpleException("Failed to commit: " + e.getMessage()); @@ -274,35 +259,16 @@ public String commit( public String push( @SpelFunctionParam(name = "sourceDir", desc = "directory inside a git working tree") String sourceDir, @SpelFunctionParam(name = "branchName", desc = "name of the branch to push") String branchName) { - - log.info("PUSH DEBUG: JGit version = {}", - org.eclipse.jgit.lib.Constants.class - .getPackage() - .getImplementationVersion()); - - - log.info("PUSH DEBUG: JGit loaded from = {}", - org.eclipse.jgit.lib.GcConfig.class - .getProtectionDomain() - .getCodeSource() - .getLocation()); - try (var git = openGit(sourceDir)) { if (git == null) { throw new FcliSimpleException("Not a git repository: " + sourceDir); } - var repo = git.getRepository(); - - // ✅ Select remote var remote = selectRemote(repo); if (remote == null) remote = "origin"; - - // ✅ Ensure branch checkout (handles detached HEAD) try { git.checkout().setName(branchName).call(); } catch (Exception e) { - // fallback: recreate branch git.checkout() .setCreateBranch(true) .setName(branchName) @@ -310,49 +276,32 @@ public String push( .call(); } - // ✅ Get and fix remote URL var remoteUrl = repo.getConfig().getString("remote", remote, "url"); if (remoteUrl != null && !remoteUrl.endsWith(".git")) { remoteUrl = remoteUrl + ".git"; repo.getConfig().setString("remote", remote, "url", remoteUrl); repo.getConfig().save(); } - - log.info("PUSH DEBUG: remote={}", remote); - log.info("PUSH DEBUG: remoteUrl={}", remoteUrl); - log.info("PUSH DEBUG: branchName={}", branchName); - - // ✅ Detect credentials CredentialsProvider credentialsProvider = detectCredentialsProvider(); - // ✅ Force GitHub token explicitly (stronger than relying only on helper) var token = System.getenv("GITHUB_TOKEN"); - log.info("PUSH DEBUG: GITHUB_TOKEN present={}", token != null); if (token != null) { - log.info("PUSH DEBUG: Overriding credentials with explicit GITHUB_TOKEN"); credentialsProvider = new UsernamePasswordCredentialsProvider("x-access-token", token); } if (credentialsProvider == null) { - log.warn("PUSH DEBUG: No credentials provider detected - push will likely fail"); + log.debug("PUSH DEBUG: No credentials provider detected - push will likely fail"); } else { - log.info("PUSH DEBUG: Using credentials provider={}", credentialsProvider.getClass().getName()); + log.debug("PUSH DEBUG: Using credentials provider={}", credentialsProvider.getClass().getName()); } - - // ✅ Prepare refspec String fullBranchRef = "refs/heads/" + branchName; var refSpec = new RefSpec(fullBranchRef + ":" + fullBranchRef); - log.info("PUSH DEBUG: refSpec={}", fullBranchRef); - - // ✅ Fetch with credentials (important for CI consistency) var fetchCmd = git.fetch().setRemote(remote); if (credentialsProvider != null) { fetchCmd.setCredentialsProvider(credentialsProvider); } fetchCmd.call(); - - // ✅ Push (NO pushAll) var pushCmd = git.push() .setRemote(remote) .setRefSpecs(refSpec) @@ -361,34 +310,18 @@ public String push( if (credentialsProvider != null) { pushCmd.setCredentialsProvider(credentialsProvider); } - var results = pushCmd.call(); - // ✅ Set upstream AFTER successful push StoredConfig config = repo.getConfig(); config.setString("branch", branchName, "remote", remote); config.setString("branch", branchName, "merge", fullBranchRef); config.save(); boolean success = false; - for (var result : results) { - log.info("PUSH DEBUG: --- PushResult Start ---"); - var messages = result.getMessages(); - if (!StringUtils.isBlank(messages)) { - log.warn("PUSH DEBUG: Remote messages:\n{}", messages); - } - for (var update : result.getRemoteUpdates()) { var status = update.getStatus(); - - log.info("PUSH DEBUG: Update remoteName={}", update.getRemoteName()); - log.info("PUSH DEBUG: Update status={}", status); - log.info("PUSH DEBUG: Update srcRef={}", update.getSrcRef()); - log.info("PUSH DEBUG: Update dstRef={}", update.getRemoteName()); - log.info("PUSH DEBUG: Update message={}", update.getMessage()); - switch (status) { case OK: case UP_TO_DATE: @@ -410,27 +343,17 @@ public String push( ); } } - - log.info("PUSH DEBUG: --- PushResult End ---"); } if (!success) { throw new FcliSimpleException("Push completed but no refs were updated (likely auth or permission issue)"); } - - log.info("Successfully pushed branch: {}", branchName); return fullBranchRef; - } catch (Exception e) { - // ✅ Deep root cause extraction Throwable root = e; while (root.getCause() != null) { root = root.getCause(); } - - log.error("PUSH DEBUG: Root cause type={}", root.getClass().getName()); - log.error("PUSH DEBUG: Root cause message={}", root.getMessage(), root); - throw new FcliSimpleException( "Failed to push (root cause): " + root.getClass().getName() + " - " + root.getMessage(), e @@ -441,32 +364,23 @@ public String push( @SpelFunction(cat=util, desc="Detects the repository owner from CI environment variables. Checks GITHUB_REPOSITORY_OWNER (GitHub), CI_PROJECT_NAMESPACE (GitLab), BUILD_REPOSITORY_ID (Azure DevOps), or BITBUCKET_WORKSPACE (Bitbucket). Returns null if not running in a supported CI system.", returns="The repository owner/namespace or null if not detectable") public String ciRepositoryOwner() { - // GitHub Actions var owner = EnvHelper.env("GITHUB_REPOSITORY_OWNER"); if (StringUtils.isNotBlank(owner)) { - log.debug("ciRepositoryOwner: Detected from GITHUB_REPOSITORY_OWNER={}", owner); return owner; } - // GitLab CI owner = EnvHelper.env("CI_PROJECT_NAMESPACE"); if (StringUtils.isNotBlank(owner)) { - log.debug("ciRepositoryOwner: Detected from CI_PROJECT_NAMESPACE={}", owner); return owner; } - // Azure DevOps var buildRepoId = EnvHelper.env("BUILD_REPOSITORY_ID"); owner = EnvHelper.env("SYSTEM_TEAMPROJECT"); if (StringUtils.isNotBlank(buildRepoId) && StringUtils.isNotBlank(owner)) { - log.debug("ciRepositoryOwner: Detected from Azure DevOps SYSTEM_TEAMPROJECT={}", owner); return owner; } - // Bitbucket Pipelines owner = EnvHelper.env("BITBUCKET_WORKSPACE"); if (StringUtils.isNotBlank(owner)) { - log.debug("ciRepositoryOwner: Detected from BITBUCKET_WORKSPACE={}", owner); return owner; } - log.debug("ciRepositoryOwner: No CI environment detected, returning null"); return null; } @@ -474,13 +388,10 @@ public String ciRepositoryOwner() { returns="The default branch name (e.g. 'main', 'master', 'develop') or null if not detectable") public String defaultBranch( @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir) { - // GitLab CI provides CI_DEFAULT_BRANCH var defaultBranch = EnvHelper.env("CI_DEFAULT_BRANCH"); if (StringUtils.isNotBlank(defaultBranch)) { - log.debug("defaultBranch: Detected from CI_DEFAULT_BRANCH={}", defaultBranch); return defaultBranch; } - // Try reading from local git remote HEAD (set by git clone) try (var git = openGit(sourceDir)) { if (git == null) { return null; } var repo = git.getRepository(); @@ -489,7 +400,6 @@ public String defaultBranch( var ref = repo.exactRef("refs/remotes/origin/HEAD"); if (ref != null && ref.getTarget() != null) { var target = ref.getTarget().getName(); - // target is like refs/remotes/origin/main if (target.startsWith("refs/remotes/origin/")) { return target.substring("refs/remotes/origin/".length()); } diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml index 8e2a714eab6..784f99b6b7f 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml @@ -12,10 +12,10 @@ usage: Configuration via environment variables: - `SOURCE_DIR` — Source directory where changes are detected (default: CI workspace or current directory) - - `PR_TITLE` — PR/MR title (default: "fix: Fortify auto-remediation fixes") + - `PR_TITLE` — PR/MR title (default: "fix: Fortify auto-remediation fixes [generated by fcli]") - `PR_BODY` — PR/MR body (default: auto-generated description) - `PR_BRANCH_PREFIX` — Branch name prefix (default: "fcli/remediation") - - `PR_COMMIT_MESSAGE` — Commit message (default: "fix: apply Fortify auto-remediation fixes") + - `PR_COMMIT_MESSAGE` — Commit message (default: "fix: apply Fortify auto-remediation fixes [generated by fcli]") config: output: immediate diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml index e5321eef16a..7bfe66be395 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml @@ -12,10 +12,10 @@ usage: Configuration via environment variables: - `SOURCE_DIR` — Source directory where changes are detected (default: CI workspace or current directory) - - `PR_TITLE` — PR/MR title (default: "fix: Fortify auto-remediation fixes") + - `PR_TITLE` — PR/MR title (default: "fix: Fortify auto-remediation fixes [generated by fcli]") - `PR_BODY` — PR/MR body (default: auto-generated description) - `PR_BRANCH_PREFIX` — Branch name prefix (default: "fcli/remediation") - - `PR_COMMIT_MESSAGE` — Commit message (default: "fix: apply Fortify auto-remediation fixes") + - `PR_COMMIT_MESSAGE` — Commit message (default: "fix: apply Fortify auto-remediation fixes [generated by fcli]") config: output: immediate From ee50a4eb4f59d492352a63bd8e3db7e77fae3148 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Fri, 19 Jun 2026 18:43:09 +0530 Subject: [PATCH 23/46] Revamped the PR creation logic --- .../actions/zip/commit-and-create-pr.yaml | 94 ++++++++++ .../actions/zip/git-stage-commit-push.yaml | 146 +++++++++++++++ .../actions/zip/pr-or-mr-create.yaml | 128 +++++++++++++ .../helper/ci/ActionCiSpelFunctions.java | 1 + .../ActionAdoCredentialsProvider.java | 57 ++++++ .../ActionBitbucketCredentialsProvider.java | 57 ++++++ .../ActionGitHubCredentialsProvider.java | 63 +++++++ .../ActionGitLabCredentialsProvider.java | 57 ++++++ .../CredentialsProviderFactory.java | 100 ++++++++++ .../IActionCredentialsProvider.java | 48 +++++ .../helper/git/ActionGitSpelFunctions.java | 72 ++++--- .../fod/actions/zip/apply-remediations.yaml | 39 ---- .../com/fortify/cli/fod/actions/zip/ci.yaml | 23 ++- .../cli/fod/actions/zip/create-pr.yaml | 177 ------------------ .../ssc/actions/zip/apply-remediations.yaml | 47 ----- .../com/fortify/cli/ssc/actions/zip/ci.yaml | 21 ++- .../cli/ssc/actions/zip/create-pr.yaml | 177 ------------------ 17 files changed, 836 insertions(+), 471 deletions(-) create mode 100644 fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml create mode 100644 fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml create mode 100644 fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml create mode 100644 fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionAdoCredentialsProvider.java create mode 100644 fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionBitbucketCredentialsProvider.java create mode 100644 fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionGitHubCredentialsProvider.java create mode 100644 fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionGitLabCredentialsProvider.java create mode 100644 fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/CredentialsProviderFactory.java create mode 100644 fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/IActionCredentialsProvider.java delete mode 100644 fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/apply-remediations.yaml delete mode 100644 fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml delete mode 100644 fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/apply-remediations.yaml delete mode 100644 fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml new file mode 100644 index 00000000000..9b5eab8941f --- /dev/null +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml @@ -0,0 +1,94 @@ +# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json +author: Fortify +usage: + header: Stage, commit, push and create a pull/merge request + description: | + Convenience wrapper that combines `git-stage-commit-push` and `pr-or-mr-create` + into a single action. Detects local changes, commits them to a new branch, pushes + to the remote, then raises a pull request (GitHub) or merge request (GitLab). + + For more control, run `git-stage-commit-push` and `pr-or-mr-create` separately. + + This action requires: + - Git repository access (local clone with a configured remote) + - A CI token: GITHUB_TOKEN/GH_TOKEN (GitHub), CI_JOB_TOKEN (GitLab), + SYSTEM_ACCESSTOKEN (Azure DevOps), or BITBUCKET_TOKEN (Bitbucket) + + Configuration via CLI options or environment variables: + - `--source-dir` / `SOURCE_DIR` — Directory where changes are detected (default: '.') + - `--branch-prefix` / `BRANCH_PREFIX` — Branch name prefix (default: "fcli/aviator-remediations") + - `--title` / `PR_TITLE` — PR/MR title (default: "fix: Fortify auto-remediation fixes [Generated by fcli]") + - `--body` / `PR_BODY` — PR/MR description (default: generic message) + - `--base-branch` / `BASE_BRANCH` — Target branch for the PR/MR (default: auto-detected from remote, fallback "main") + - `--author-name` / `GIT_AUTHOR_NAME` — Commit author name (default: "fcli-actions[bot]") + - `--author-email` / `GIT_AUTHOR_EMAIL` — Commit author email (default: "fcli-actions@opentext.com") + +options: + - name: source-dir + description: Directory where changes are detected + defaultValue: "${#env('SOURCE_DIR')?:global.ci.sourceDir?:'.'}" + + - name: branch-prefix + description: Prefix for the new branch name + defaultValue: "${#env('BRANCH_PREFIX')?:'fcli/aviator-remediations'}" + + - name: title + description: PR/MR title + defaultValue: "${#env('PR_TITLE')?:'fix: Fortify auto-remediation fixes [Generated by fcli aviator]'}" + + - name: body + description: PR/MR description + defaultValue: "${#env('PR_BODY')?:'This pull request contains changes applied by fcli aviator.'}" + + - name: author-name + description: Git author name for the commit + defaultValue: "${#env('GIT_AUTHOR_NAME')?:'fcli-aviator[bot]'}" + + - name: author-email + description: Git author email for the commit + defaultValue: "${#env('GIT_AUTHOR_EMAIL')?:'fcli-aviator@opentext.com'}" + +config: + output: immediate + +steps: + - var.set: + sourceDir: "${#opt('source-dir', global.ci.sourceDir?:'.')}" + branchPrefix: "${#opt('branch-prefix', 'fcli/aviator-remediations')}" + prTitle: "${#opt('title', 'fix: Fortify auto-remediation fixes [Generated by fcli aviator]')}" + prBody: "${#opt('body', 'This pull request contains changes applied by fcli aviator.')}" + baseBranch: "${#ifBlank(#opt('base-branch', ''), #ifBlank(#git.defaultBranch(sourceDir), 'main'))}" + authorName: "${#opt('author-name', 'fcli-aviator[bot]')}" + authorEmail: "${#opt('author-email', 'fcli-aviator@opentext.com')}" + + # Step 1: Stage, commit and push + - action.run: + action: git-stage-commit-push + options: + source-dir: "${sourceDir}" + branch-prefix: "${branchPrefix}" + author-name: "${authorName}" + author-email: "${authorEmail}" + - var.set: + gitResult: ${result} + + - if: "${gitResult.status=='no_changes'}" + do: + - log.info: "No changes detected, skipping PR/MR creation." + - exit: 0 + + # Step 2: Create PR/MR from the pushed branch + - action.run: + action: pr-or-mr-create + options: + source-dir: "${sourceDir}" + source-branch: "${gitResult.branchName}" + base-branch: "${baseBranch}" + title: "${prTitle}" + body: "${prBody}" + - var.set: + prResult: ${result} + + - log.info: "commit-and-create-pr completed: git=${gitResult.status}, pr=${prResult.status}" + - if: "${prResult.status=='success'}" + log.info: "PR/MR URL: ${prResult.prLink}" \ No newline at end of file diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml new file mode 100644 index 00000000000..4737338c215 --- /dev/null +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml @@ -0,0 +1,146 @@ +# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json +author: Fortify +usage: + header: Stage, commit and push local changes to a new branch + description: | + This action detects local file changes, creates a new branch, stages and commits + the changes, then pushes the branch to the remote repository. Credentials are + auto-detected from the CI environment (GitHub Actions, GitLab CI, Azure DevOps, + Bitbucket Pipelines). + + This action requires: + - Git repository access (local clone with a configured remote) + - A CI token with push permissions (GITHUB_TOKEN, GH_TOKEN, CI_JOB_TOKEN, + SYSTEM_ACCESSTOKEN, or BITBUCKET_TOKEN) + + Configuration via CLI options or environment variables: + - `--source-dir` / `SOURCE_DIR` — Directory where changes are detected (default: '.') + - `--branch-prefix` / `BRANCH_PREFIX` — Branch name prefix (default: "fcli/aviator-remediations") + - `--commit-message` / `COMMIT_MESSAGE` — Commit message (default: "fix: apply automated fixes [generated by fcli aviator]") + - `--author-name` / `GIT_AUTHOR_NAME` — Commit author name (default: "fcli-aviator[bot]") + - `--author-email` / `GIT_AUTHOR_EMAIL` — Commit author email (default: "fcli-aviator@opentext.com") + + Output variables (accessible by subsequent actions or scripts): + - `result.status` — "success", "no_changes", or "error" + - `result.branchName` — Name of the newly created branch + - `result.commitSha` — SHA of the created commit + - `result.pushedRef` — Full ref pushed to the remote + +options: + - name: source-dir + description: Directory where changes are detected + defaultValue: "${#env('SOURCE_DIR')?:global.ci.sourceDir?:'.'}" + + - name: branch-prefix + description: Prefix for the new branch name + defaultValue: "${#env('BRANCH_PREFIX')?:'fcli/aviator-remediations'}" + + - name: commit-message + description: Git commit message + defaultValue: "${#env('COMMIT_MESSAGE')?:'fix: apply automated fixes [generated by fcli aviator]'}" + + - name: author-name + description: Git author name for the commit + defaultValue: "${#env('GIT_AUTHOR_NAME')?:'fcli-aviator[bot]'}" + + - name: author-email + description: Git author email for the commit + defaultValue: "${#env('GIT_AUTHOR_EMAIL')?:'fcli-aviator@opentext.com'}" + +config: + output: immediate + +steps: + - var.set: + sourceDir: "${#opt('source-dir', global.ci.sourceDir?:'.')}" + branchPrefix: "${#opt('branch-prefix', 'fcli/aviator-remediations')}" + commitMessage: "${#opt('commit-message', 'fix: apply automated fixes [generated by fcli aviator]')}" + authorName: "${#opt('author-name', 'fcli-aviator[bot]')}" + authorEmail: "${#opt('author-email', 'fcli-aviator@opentext.com')}" + + - log.debug: "Action configuration: sourceDir=${sourceDir}, branchPrefix=${branchPrefix}, author=${authorName} <${authorEmail}>" + + # Validate git repository + - var.set: + gitRepoInfo: ${#git.localRepo(sourceDir)} + - if: ${gitRepoInfo==null} + throw: "Source directory '${sourceDir}' is not a git repository." + - if: "${#isBlank(gitRepoInfo.repository.remoteUrl)}" + throw: "Git repository has no remote URL configured. A remote is required for push." + - log.info: "Git repository: remote=${gitRepoInfo.repository.remoteUrl}, branch=${gitRepoInfo.branch.short?:'detached HEAD'}" + + # Check for changes + - var.set: + hasChanges: ${#git.hasChanges(sourceDir)} + - if: ${!hasChanges} + do: + - log.info: "No changes detected in ${sourceDir}, skipping commit and push." + - var.set: + result: ${T(java.util.LinkedHashMap).new()} + result.status: "no_changes" + result.branchName: "" + result.commitSha: "" + result.pushedRef: "" + - exit: 0 + - log.info: "Changes detected in ${sourceDir}, proceeding." + + # Set git author identity + - var.set: + gitAuthorEnv: ${#env.set('GIT_AUTHOR_NAME', authorName).put('GIT_AUTHOR_EMAIL', authorEmail)} + on.fail: + - log.warn: "Warning: Unable to set git author environment variables" + + # Create branch + - var.set: + branchName: ${#git.createBranch(sourceDir, branchPrefix)} + on.fail: + - log.warn: + msg: "Failed to create branch with prefix '${branchPrefix}'" + cause: ${lastException} + - throw: ${lastException} + - log.info: "Created branch: ${branchName}" + + # Stage all changes + - var.set: + staged: ${#git.addAll(sourceDir)} + on.fail: + - log.warn: + msg: "Failed to stage changes in ${sourceDir}" + cause: ${lastException} + - throw: ${lastException} + - log.debug: "All changes staged successfully" + + # Commit + - var.set: + commitSha: "${#git.commit(sourceDir, commitMessage)}" + on.fail: + - log.warn: + msg: "Failed to commit staged changes" + cause: ${lastException} + - throw: ${lastException} + - log.info: "Committed changes: ${commitSha}" + + # Push + - var.set: + pushedRef: ${#git.push(sourceDir, branchName)} + on.fail: + - var.set: + remoteUrl: "${gitRepoInfo.repository.remoteUrl?:'unknown'}" + exceptionMsg: "${lastException.message}" + - log.warn: + msg: "Failed to push branch '${branchName}' to remote '${remoteUrl}': ${exceptionMsg}" + cause: ${lastException} + - if: "${exceptionMsg.contains('No credentials available')}" + log.warn: "DIAGNOSIS: Push failed due to missing credentials. Set one of: GITHUB_TOKEN/GH_TOKEN, CI_JOB_TOKEN, SYSTEM_ACCESSTOKEN, or BITBUCKET_TOKEN" + - throw: + msg: "Failed to push branch '${branchName}' to remote" + cause: ${lastException} + - log.info: "Pushed branch to remote: ${pushedRef}" + + - var.set: + result: ${T(java.util.LinkedHashMap).new()} + result.status: "success" + result.branchName: "${branchName}" + result.commitSha: "${commitSha}" + result.pushedRef: "${pushedRef}" + - log.info: "git-stage-commit-push completed: branch=${result.branchName}, commit=${result.commitSha}" diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml new file mode 100644 index 00000000000..54929c87168 --- /dev/null +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml @@ -0,0 +1,128 @@ +# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json +author: Fortify +usage: + header: Create a pull request or merge request on GitHub or GitLab + description: | + This action creates a pull request (GitHub) or merge request (GitLab) for an + existing branch. The repository platform is auto-detected from the git remote URL. + Only GitHub and GitLab are supported; for other platforms the branch must be + promoted to a PR/MR manually. + + Typically used after `git-stage-commit-push` to raise a PR/MR for the pushed branch. + + This action requires: + - Git repository with a remote pointing to GitHub or GitLab + - A CI token with PR/MR creation permissions (GITHUB_TOKEN/GH_TOKEN or CI_JOB_TOKEN) + + Configuration via CLI options or environment variables: + - `--source-dir` / `SOURCE_DIR` — Local repo directory used to detect the platform (default: '.') + - `--source-branch` / `SOURCE_BRANCH` — Branch to raise the PR/MR from (required) + - `--base-branch` / `BASE_BRANCH` — Target branch for the PR/MR (default: auto-detected from remote, fallback "main") + - `--title` / `PR_TITLE` — PR/MR title (default: "fix: Fortify auto-remediation fixes") + - `--body` / `PR_BODY` — PR/MR description (default: generic message) + + Output variables: + - `result.status` — "success", "no_pr_support", or "error" + - `result.prLink` — URL of the created PR/MR (empty on failure) + - `result.platform` — Detected repository platform ("github", "gitlab", or "unknown") + +options: + - name: source-dir + description: Local repo directory (used to detect the repository platform) + defaultValue: "${#env('SOURCE_DIR')?:global.ci.sourceDir?:'.'}" + + - name: source-branch + description: Source branch to raise the PR/MR from + required: true + defaultValue: "${#env('SOURCE_BRANCH')?:''}" + + - name: title + description: PR/MR title + defaultValue: "${#env('PR_TITLE')?:'fix: Fortify auto-remediation fixes [Generated by fcli aviator]'}" + + - name: body + description: PR/MR description + defaultValue: "${#env('PR_BODY')?:'This pull request contains changes applied by fcli aviator.'}" + +config: + output: immediate + +steps: + - var.set: + sourceDir: "${#opt('source-dir', global.ci.sourceDir?:'.')}" + sourceBranch: "${#opt('source-branch', '')}" + baseBranch: "${#ifBlank(#opt('base-branch', ''), #ifBlank(#git.defaultBranch(sourceDir), 'main'))}" + prTitle: "${#opt('title', 'fix: Fortify auto-remediation fixes [Generated by fcli aviator]')}" + prBody: "${#opt('body', 'This pull request contains changes applied by fcli aviator.')}" + + - if: "${#isBlank(sourceBranch)}" + throw: "Option '--source-branch' is required. Pass the branch created by git-stage-commit-push." + + - log.debug: "PR/MR configuration: platform detection from '${sourceDir}', source=${sourceBranch}, base=${baseBranch}" + + # Detect repository platform from git remote URL + - var.set: + repoPlatform: ${#git.repositoryPlatform(sourceDir)} + - log.debug: "Detected repository platform: ${repoPlatform}" + + - var.set: + result: ${T(java.util.LinkedHashMap).new()} + result.status: "error" + result.prLink: "" + result.platform: "${repoPlatform}" + + # GitHub PR creation + - if: "${repoPlatform=='github'}" + do: + - var.set: + repoOwner: "${#git.ciRepositoryOwner()}" + prHead: "${repoOwner}:${sourceBranch}" + - log.debug: "Creating GitHub PR: head=${prHead}, base=${baseBranch}, title=${prTitle}" + - var.set: + pr: ${#_ci.detect().repo().createPullRequest(prTitle, prHead, baseBranch, prBody)} + on.fail: + - log.warn: + msg: "Failed to create GitHub Pull Request from '${sourceBranch}' to '${baseBranch}'" + cause: ${lastException} + - log.warn: "Troubleshooting: Verify branch '${sourceBranch}' exists on remote and base branch '${baseBranch}' is valid" + - var.set: + result.status: "error" + result.errorMessage: "${lastException.message}" + - if: ${pr!=null} + do: + - var.set: + result.status: "success" + result.prLink: "${pr.html_url}" + - log.info: "Created GitHub Pull Request #${pr.number}: ${pr.html_url}" + + # GitLab MR creation + - if: "${repoPlatform=='gitlab'}" + do: + - log.debug: "Creating GitLab Merge Request: source=${sourceBranch}, target=${baseBranch}, title=${prTitle}" + - var.set: + mr: ${#_ci.detect().project().createMergeRequest(prTitle, sourceBranch, baseBranch, prBody)} + on.fail: + - log.warn: + msg: "Failed to create GitLab Merge Request from '${sourceBranch}' to '${baseBranch}'" + cause: ${lastException} + - log.warn: "Troubleshooting: Verify branch '${sourceBranch}' exists on remote and base branch '${baseBranch}' is valid" + - var.set: + result.status: "error" + result.errorMessage: "${lastException.message}" + - if: ${mr!=null} + do: + - var.set: + result.status: "success" + result.prLink: "${mr.web_url}" + - log.info: "Created GitLab Merge Request !${mr.iid}: ${mr.web_url}" + + # Unsupported repository platform + - if: "${repoPlatform!='github' && repoPlatform!='gitlab'}" + do: + - log.warn: "PR/MR creation is not supported for repository platform '${repoPlatform}'. Branch '${sourceBranch}' has been pushed; please create a PR/MR manually." + - var.set: + result.status: "no_pr_support" + + - log.info: "pr-or-mr-create completed: status=${result.status}, platform=${result.platform}" + - if: "${result.status=='success'}" + log.info: "PR/MR URL: ${result.prLink}" diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/ActionCiSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/ActionCiSpelFunctions.java index b215feca546..2b71e8053d9 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/ActionCiSpelFunctions.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/ActionCiSpelFunctions.java @@ -73,6 +73,7 @@ public IActionSpelFunctions detect() { return ActionUnknownCiSpelFunctions.INSTANCE; } + /** * Unknown/unsupported CI system implementation. * Used when no known CI system is detected. diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionAdoCredentialsProvider.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionAdoCredentialsProvider.java new file mode 100644 index 00000000000..ea69a1ccf36 --- /dev/null +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionAdoCredentialsProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2026 Open Text. + * + * The only warranties for products and services of Open Text + * and its affiliates and licensors ("Open Text") are as may + * be set forth in the express warranty statements accompanying + * such products and services. Nothing herein should be construed + * as constituting an additional warranty. Open Text shall not be + * liable for technical or editorial errors or omissions contained + * herein. The information contained herein is subject to change + * without notice. + */ +package com.fortify.cli.common.action.helper.credential; + +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; + +import com.formkiq.graalvm.annotations.Reflectable; +import com.fortify.cli.common.util.EnvHelper; + +/** + * Provides Azure DevOps credentials for Git operations. + * Retrieves Azure DevOps token from SYSTEM_ACCESSTOKEN environment variable (set by ADO Pipeline). + * + * @author Sangamesh Vijayakumar + */ +@Reflectable +public class ActionAdoCredentialsProvider implements IActionCredentialsProvider { + + @Override + public CredentialsProvider getCredentialsProvider() { + String token = getAdoToken(); + if (token == null) { + return null; + } + // Azure DevOps uses empty string as username with token as password + return new UsernamePasswordCredentialsProvider("", token); + } + + @Override + public String getCiSystemType() { + return "ado"; + } + + @Override + public boolean isAvailable() { + return getAdoToken() != null; + } + + private String getAdoToken() { + String token = EnvHelper.env("SYSTEM_ACCESSTOKEN"); + if (token != null && !token.isBlank()) { + return token; + } + return null; + } +} diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionBitbucketCredentialsProvider.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionBitbucketCredentialsProvider.java new file mode 100644 index 00000000000..be56dc901fd --- /dev/null +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionBitbucketCredentialsProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2026 Open Text. + * + * The only warranties for products and services of Open Text + * and its affiliates and licensors ("Open Text") are as may + * be set forth in the express warranty statements accompanying + * such products and services. Nothing herein should be construed + * as constituting an additional warranty. Open Text shall not be + * liable for technical or editorial errors or omissions contained + * herein. The information contained herein is subject to change + * without notice. + */ +package com.fortify.cli.common.action.helper.credential; + +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; + +import com.formkiq.graalvm.annotations.Reflectable; +import com.fortify.cli.common.util.EnvHelper; + +/** + * Provides Bitbucket credentials for Git operations. + * Retrieves Bitbucket token from BITBUCKET_TOKEN environment variable. + * + * @author Sangamesh Vijayakumar + */ +@Reflectable +public class ActionBitbucketCredentialsProvider implements IActionCredentialsProvider { + + @Override + public CredentialsProvider getCredentialsProvider() { + String token = getBitbucketToken(); + if (token == null) { + return null; + } + // Bitbucket uses "x-token-auth" as username with token as password + return new UsernamePasswordCredentialsProvider("x-token-auth", token); + } + + @Override + public String getCiSystemType() { + return "bitbucket"; + } + + @Override + public boolean isAvailable() { + return getBitbucketToken() != null; + } + + private String getBitbucketToken() { + String token = EnvHelper.env("BITBUCKET_TOKEN"); + if (token != null && !token.isBlank()) { + return token; + } + return null; + } +} diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionGitHubCredentialsProvider.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionGitHubCredentialsProvider.java new file mode 100644 index 00000000000..2ce876da995 --- /dev/null +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionGitHubCredentialsProvider.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021-2026 Open Text. + * + * The only warranties for products and services of Open Text + * and its affiliates and licensors ("Open Text") are as may + * be set forth in the express warranty statements accompanying + * such products and services. Nothing herein should be construed + * as constituting an additional warranty. Open Text shall not be + * liable for technical or editorial errors or omissions contained + * herein. The information contained herein is subject to change + * without notice. + */ +package com.fortify.cli.common.action.helper.credential; + +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; + +import com.formkiq.graalvm.annotations.Reflectable; +import com.fortify.cli.common.util.EnvHelper; + +/** + * Provides GitHub credentials for Git operations. + * Retrieves GitHub token from GITHUB_TOKEN or GH_TOKEN environment variables. + * + * @author Sangamesh Vijayakumar + */ +@Reflectable +public class ActionGitHubCredentialsProvider implements IActionCredentialsProvider { + + @Override + public CredentialsProvider getCredentialsProvider() { + String token = getGitHubToken(); + if (token == null) { + return null; + } + // GitHub uses "x-access-token" as username with token as password + return new UsernamePasswordCredentialsProvider("x-access-token", token); + } + + @Override + public String getCiSystemType() { + return "github"; + } + + @Override + public boolean isAvailable() { + return getGitHubToken() != null; + } + + private String getGitHubToken() { + // Check GITHUB_TOKEN first (standard GitHub Actions variable) + String token = EnvHelper.env("GITHUB_TOKEN"); + if (token != null && !token.isBlank()) { + return token; + } + // Fall back to GH_TOKEN (GitHub CLI standard variable) + token = EnvHelper.env("GH_TOKEN"); + if (token != null && !token.isBlank()) { + return token; + } + return null; + } +} diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionGitLabCredentialsProvider.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionGitLabCredentialsProvider.java new file mode 100644 index 00000000000..1d13f365e5c --- /dev/null +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/ActionGitLabCredentialsProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2026 Open Text. + * + * The only warranties for products and services of Open Text + * and its affiliates and licensors ("Open Text") are as may + * be set forth in the express warranty statements accompanying + * such products and services. Nothing herein should be construed + * as constituting an additional warranty. Open Text shall not be + * liable for technical or editorial errors or omissions contained + * herein. The information contained herein is subject to change + * without notice. + */ +package com.fortify.cli.common.action.helper.credential; + +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; + +import com.formkiq.graalvm.annotations.Reflectable; +import com.fortify.cli.common.util.EnvHelper; + +/** + * Provides GitLab credentials for Git operations. + * Retrieves GitLab token from CI_JOB_TOKEN environment variable (set by GitLab CI/CD). + * + * @author Sangamesh Vijayakumar + */ +@Reflectable +public class ActionGitLabCredentialsProvider implements IActionCredentialsProvider { + + @Override + public CredentialsProvider getCredentialsProvider() { + String token = getGitLabToken(); + if (token == null) { + return null; + } + // GitLab uses "gitlab-ci-token" as username with token as password + return new UsernamePasswordCredentialsProvider("gitlab-ci-token", token); + } + + @Override + public String getCiSystemType() { + return "gitlab"; + } + + @Override + public boolean isAvailable() { + return getGitLabToken() != null; + } + + private String getGitLabToken() { + String token = EnvHelper.env("CI_JOB_TOKEN"); + if (token != null && !token.isBlank()) { + return token; + } + return null; + } +} diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/CredentialsProviderFactory.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/CredentialsProviderFactory.java new file mode 100644 index 00000000000..a401a033fb5 --- /dev/null +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/CredentialsProviderFactory.java @@ -0,0 +1,100 @@ +/* + * Copyright 2021-2026 Open Text. + * + * The only warranties for products and services of Open Text + * and its affiliates and licensors ("Open Text") are as may + * be set forth in the express warranty statements accompanying + * such products and services. Nothing herein should be construed + * as constituting an additional warranty. Open Text shall not be + * liable for technical or editorial errors or omissions contained + * herein. The information contained herein is subject to change + * without notice. + */ +package com.fortify.cli.common.action.helper.credential; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jgit.transport.CredentialsProvider; + +import com.formkiq.graalvm.annotations.Reflectable; + +/** + * Factory for creating and detecting CI system credentials providers. + * Manages credential provider instantiation and selection based on CI system or availability. + * + * @author Sangamesh Vijayakumar + */ +@Reflectable +public class CredentialsProviderFactory { + + // Ordered list of providers - GitHub checked first to maintain backward compatibility + private static final List PROVIDERS = Arrays.asList( + new ActionGitHubCredentialsProvider(), + new ActionGitLabCredentialsProvider(), + new ActionAdoCredentialsProvider(), + new ActionBitbucketCredentialsProvider() + ); + + /** + * Get the credentials provider for a specific CI system. + * + * @param ciSystemType "github", "gitlab", "ado", or "bitbucket" + * @return IActionCredentialsProvider for the specified system, or null if not found + */ + public static IActionCredentialsProvider getProvider(String ciSystemType) { + if (ciSystemType == null) { + return null; + } + + return PROVIDERS.stream() + .filter(p -> p.getCiSystemType().equalsIgnoreCase(ciSystemType)) + .findFirst() + .orElse(null); + } + + /** + * Auto-detect and return the first available credentials provider. + * Checks providers in order: GitHub, GitLab, ADO, Bitbucket. + * This maintains backward compatibility where GitHub token takes priority. + * + * @return IActionCredentialsProvider for the first detected CI system, + * or null if no credentials are available + */ + public static IActionCredentialsProvider detectAndGetProvider() { + return PROVIDERS.stream() + .filter(IActionCredentialsProvider::isAvailable) + .findFirst() + .orElse(null); + } + + /** + * Get JGit CredentialsProvider from auto-detected CI system. + * Convenience method combining detection and credential provider retrieval. + * + * @return JGit CredentialsProvider for the detected CI system, + * or null if no credentials are available + */ + public static CredentialsProvider detectAndGetJGitProvider() { + IActionCredentialsProvider provider = detectAndGetProvider(); + return provider != null ? provider.getCredentialsProvider() : null; + } + + /** + * Check if any credentials are available. + * + * @return true if at least one CI system has credentials configured + */ + public static boolean hasCredentials() { + return PROVIDERS.stream().anyMatch(IActionCredentialsProvider::isAvailable); + } + + /** + * Get all registered credential providers. + * + * @return List of all IActionCredentialsProvider implementations + */ + public static List getAllProviders() { + return PROVIDERS; + } +} diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/IActionCredentialsProvider.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/IActionCredentialsProvider.java new file mode 100644 index 00000000000..f3055313f9a --- /dev/null +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/credential/IActionCredentialsProvider.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2026 Open Text. + * + * The only warranties for products and services of Open Text + * and its affiliates and licensors ("Open Text") are as may + * be set forth in the express warranty statements accompanying + * such products and services. Nothing herein should be construed + * as constituting an additional warranty. Open Text shall not be + * liable for technical or editorial errors or omissions contained + * herein. The information contained herein is subject to change + * without notice. + */ +package com.fortify.cli.common.action.helper.credential; + +import org.eclipse.jgit.transport.CredentialsProvider; + +import com.formkiq.graalvm.annotations.Reflectable; + +/** + * Provides CI system-specific credentials for Git operations. + * Each implementation handles token retrieval and formatting for its respective CI system. + * + * @author Sangamesh Vijayakumar + */ +@Reflectable +public interface IActionCredentialsProvider { + /** + * Get the JGit CredentialsProvider for this CI system. + * + * @return JGit CredentialsProvider configured with appropriate token and username format, + * or null if credentials are not available + */ + CredentialsProvider getCredentialsProvider(); + + /** + * Get the CI system type identifier. + * + * @return "github", "gitlab", "ado", or "bitbucket" + */ + String getCiSystemType(); + + /** + * Check if credentials are available for this CI system. + * + * @return true if required environment variable is set, false otherwise + */ + boolean isAvailable(); +} diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java index 9d14018283e..994f0a03bdf 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.formkiq.graalvm.annotations.Reflectable; +import com.fortify.cli.common.action.helper.credential.CredentialsProviderFactory; import com.fortify.cli.common.ci.CiBranch; import com.fortify.cli.common.ci.CiCommit; import com.fortify.cli.common.ci.CiCommitId; @@ -411,6 +412,30 @@ public String defaultBranch( return null; } + + @SpelFunction(cat=util, desc=""" + Detects the hosting platform of the repository by parsing the git remote URL. + Returns "github" for GitHub-hosted repositories (github.com or *.github.com), + "gitlab" for GitLab-hosted repositories (gitlab.com or hostnames containing "gitlab"), + and "unknown" for any other remote or when detection fails. + This is platform detection (where the repo lives), not CI detection (where the pipeline runs). + """, + returns="\"github\", \"gitlab\", or \"unknown\"") + public String repositoryPlatform( + @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir) { + try (var git = openGit(sourceDir)) { + if (git == null) { return "unknown"; } + var repo = git.getRepository(); + var remote = selectRemote(repo); + if (remote == null) { return "unknown"; } + var remoteUrl = repo.getConfig().getString("remote", remote, "url"); + return detectPlatformFromUrl(remoteUrl); + } catch (Exception e) { + log.debug("Failed to detect repository platform", e); + return "unknown"; + } + } + private Git openGit(String sourceDir) { if (StringUtils.isBlank(sourceDir)) { return null; } try { @@ -425,28 +450,7 @@ private Git openGit(String sourceDir) { } private CredentialsProvider detectCredentialsProvider() { - // GitHub Actions / GitHub CLI - var token = EnvHelper.env("GITHUB_TOKEN"); - if (StringUtils.isBlank(token)) { token = EnvHelper.env("GH_TOKEN"); } - if (StringUtils.isNotBlank(token)) { - return new UsernamePasswordCredentialsProvider("x-access-token", token); - } - // GitLab CI - token = EnvHelper.env("CI_JOB_TOKEN"); - if (StringUtils.isNotBlank(token)) { - return new UsernamePasswordCredentialsProvider("gitlab-ci-token", token); - } - // Azure DevOps - token = EnvHelper.env("SYSTEM_ACCESSTOKEN"); - if (StringUtils.isNotBlank(token)) { - return new UsernamePasswordCredentialsProvider("", token); - } - // Bitbucket Pipelines - token = EnvHelper.env("BITBUCKET_TOKEN"); - if (StringUtils.isNotBlank(token)) { - return new UsernamePasswordCredentialsProvider("x-token-auth", token); - } - return null; + return CredentialsProviderFactory.detectAndGetJGitProvider(); } private static String selectRemote(Repository repo) { @@ -483,4 +487,26 @@ private static String[] deriveRepoNames(String fallbackShort, String remoteUrl) return new String[]{fallbackShort, null}; } } -} \ No newline at end of file + private static String detectPlatformFromUrl(String remoteUrl) { + if (StringUtils.isBlank(remoteUrl)) { return "unknown"; } + try { + String host; + var cleaned = remoteUrl.trim(); + if (cleaned.startsWith("git@")) { + // SSH: git@github.com:owner/repo.git + int colon = cleaned.indexOf(':'); + int at = cleaned.indexOf('@'); + host = (at >= 0 && colon > at) ? cleaned.substring(at + 1, colon) : null; + } else { + host = java.net.URI.create(cleaned).getHost(); + } + if (host == null) { return "unknown"; } + host = host.toLowerCase(); + if (host.equals("github.com") || host.endsWith(".github.com")) { return "github"; } + if (host.equals("gitlab.com") || host.contains("gitlab")) { return "gitlab"; } + } catch (Exception e) { + log.debug("Failed to parse remote URL for platform detection: {}", remoteUrl); + } + return "unknown"; + } +} diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/apply-remediations.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/apply-remediations.yaml deleted file mode 100644 index d2812a9a5f7..00000000000 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/apply-remediations.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json -author: Fortify -usage: - header: Apply Aviator remediations - description: | - This action applies Aviator auto-remediation fixes to source code for a given - FoD release. It downloads the audited FPR with remediation suggestions and applies - the fixes to the local source tree. - - This action requires: - - A FoD session to be active (for downloading the audited FPR with remediations) - - After running this action, any modified files can be committed and pushed using - the `create-pr` action. - -config: - output: immediate - rest.target.default: fod - mcp: exclude - -steps: - - run.fcli: - detect-env: - group: detect-env - cmd: fcli action run detect-env - status.check: true - - - var.set: - rel: ${#env('FOD_RELEASE')?:global.ci.defaultFortifyRepo} - - if: ${rel==null} - throw: FoD release must be specified through FOD_RELEASE environment variable - - - var.set: - sourceDir: ${global.ci.sourceDir?:'.'} - - - run.fcli: - APPLY_REMEDIATIONS: - cmd: fod aviator apply-remediations --rel "${rel}" --source-dir "${sourceDir}" - status.check: true diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml index 5df6ecd3665..483e46b0c19 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml @@ -136,18 +136,29 @@ steps: - var.set: { postScan.skipReason: } # Reset postScan.skipReason to allow post-scan tasks to run APPLY_REMEDIATIONS: - cmd: ${#actionCmd('APPLY_REMEDIATIONS', 'fod', 'apply-remediations')} "--progress=none" + cmd: ${#fcliCmd('APPLY_REMEDIATIONS', 'fod aviator apply-remediations')} "--rel=${global.ci.rel}" "--source-dir=${global.ci.sourceDir}" "--progress=none" skip.if-reason: - - ${#actionCmdSkipFromEnvReason('APPLY_REMEDIATIONS', true)} # Skip unless DO_APPLY_REMEDIATIONS==true or APPLY_REMEDIATIONS_ACTION/EXTRA_OPTS defined + - ${#fcliCmdSkipFromEnvReason('APPLY_REMEDIATIONS', true)} # Skip unless DO_APPLY_REMEDIATIONS==true or APPLY_REMEDIATIONS_ACTION/EXTRA_OPTS defined - ${postScan.skipReason} # Skip if no scans were run - ${#env('DO_AVIATOR_AUDIT')!='true'?'Aviator audit not enabled (DO_AVIATOR_AUDIT!=true), no remediations available':''} # Skip if Aviator audit was not enabled - ${SAST_WAIT.dependencySkipReason} # Skip if SAST scan/wait was skipped or failed + on.success: + - var.set: { remediationsApplied: 'true' } CREATE_PR: - cmd: "${#actionCmd('CREATE_PR', 'fod', 'create-pr')} --progress=none" + cmd: ${#actionCmd('CREATE_PR', '', 'action run commit-and-create-pr')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" skip.if-reason: - ${#actionCmdSkipFromEnvReason('CREATE_PR', true)} # Skip unless DO_CREATE_PR==true or CREATE_PR_EXTRA_OPTS defined - - ${APPLY_REMEDIATIONS.dependencySkipReason} # Skip if APPLY_REMEDIATIONS was skipped or failed + - ${!remediationsApplied?'Apply remediations was skipped or failed, skipping PR creation':''} + on.success: + - if: ${CREATE_PR.variables.status=='success'} + log.info: "Pull/Merge Request successfully created: ${CREATE_PR.variables.prLink}" + - if: ${CREATE_PR.variables.status=='no_changes'} + log.info: "No changes detected - PR/MR creation skipped" + - if: ${CREATE_PR.variables.status=='no_pr_support'} + log.warn: "Branch created and pushed, but PR/MR creation not supported for ${CREATE_PR.variables.ciSystem}" + - if: ${CREATE_PR.variables.status=='error'} + log.warn: "PR/MR creation failed: ${CREATE_PR.variables.errorMessage}" CHECK_POLICY: cmd: ${#actionCmd('CHECK_POLICY', 'fod', 'check-policy')} "--rel=${global.ci.rel}" "--progress=none" @@ -221,3 +232,7 @@ formatters: ${CHECK_POLICY.exitCode==0||CHECK_POLICY.exitCode==100?CHECK_POLICY.stdout:CHECK_POLICY.dependencySkipReason} ${RELEASE_SUMMARY.stdout} + + + + diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml deleted file mode 100644 index 784f99b6b7f..00000000000 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml +++ /dev/null @@ -1,177 +0,0 @@ -# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json -author: Fortify -usage: - header: Create a pull request from changed files - description: | - This action detects local file changes, creates a new branch, commits the changes, - pushes to the remote, and raises a pull/merge request via the detected CI system's API. - - This action requires: - - Git repository access (for branch/commit/push operations) - - CI system token with PR/MR creation permissions (GITHUB_TOKEN, CI_JOB_TOKEN, etc.) - - Configuration via environment variables: - - `SOURCE_DIR` — Source directory where changes are detected (default: CI workspace or current directory) - - `PR_TITLE` — PR/MR title (default: "fix: Fortify auto-remediation fixes [generated by fcli]") - - `PR_BODY` — PR/MR body (default: auto-generated description) - - `PR_BRANCH_PREFIX` — Branch name prefix (default: "fcli/remediation") - - `PR_COMMIT_MESSAGE` — Commit message (default: "fix: apply Fortify auto-remediation fixes [generated by fcli]") - -config: - output: immediate - rest.target.default: fod - run.fcli.status.log.default: true # By default, we log all exit statuses - run.fcli.status.check.default: true - mcp: exclude - -steps: - - run.fcli: - detect-env: - group: detect-env - cmd: fcli action run detect-env - status.check: true - on.fail: - - log.warn: - msg: "CI environment detection failed" - cause: ${detect-env_exception} - - - var.set: - sourceDir: ${#env('SOURCE_DIR')?:global.ci.sourceDir?:'.'} - - # Log initial git repository information for diagnostics - - var.set: - gitRepoInfo: ${#git.localRepo(sourceDir)} - - if: ${gitRepoInfo==null} - throw: "Source directory '${sourceDir}' is not a git repository. Cannot detect repository information needed for PR creation." - - if: ${#isBlank(gitRepoInfo.repository.remoteUrl)} - throw: "Git repository has no remote URL configured. Cannot create PR without remote repository access." - - if: ${gitRepoInfo!=null} - log.info: "Git repository: workspace=${gitRepoInfo.repository.workspaceDir}, remote=${gitRepoInfo.repository.remoteUrl?:'not configured'}, branch=${gitRepoInfo.branch.short_?:'detached HEAD'}" - - # Log current commit information for reference (debug level) - - if: ${gitRepoInfo.commit!=null} - log.debug: "Current HEAD: ${gitRepoInfo.commit.headId.short} - ${gitRepoInfo.commit.message.short}" - - # Check if there are changes to commit - - var.set: - hasChanges: ${#git.hasChanges(sourceDir)} - - - if: ${!hasChanges} - do: - - log.info: "No changes detected in ${sourceDir}, skipping PR creation." - - exit: 0 - - - log.info: "Changes detected in ${sourceDir}, proceeding with PR creation." - - - var.set: - branchPrefix: ${#env('PR_BRANCH_PREFIX')?:'aviator/remediations'} - commitMessage: "${#env('PR_COMMIT_MESSAGE')?:'fix: apply Fortify auto-remediation fixes [generated by fcli]'}" - prTitle: "${#env('PR_TITLE')?:'fix: Fortify auto-remediation fixes'}" - prBody: "${#env('PR_BODY')?:'This pull request contains auto-remediation fixes generated by Fortify SAST Aviator.'}" - - # Create a new branch for the changes with error handling - - var.set: - branchName: ${#git.createBranch(sourceDir, branchPrefix)} - on.fail: - - log.warn: - msg: "Failed to create branch with prefix '${branchPrefix}'" - cause: ${lastException} - - throw: ${lastException} - - log.info: "Successfully created branch: ${branchName}" - - # Stage and commit changes with error handling - - var.set: - staged: ${#git.addAll(sourceDir)} - on.fail: - - log.warn: - msg: "Failed to stage changes in ${sourceDir}" - cause: ${lastException} - - throw: ${lastException} - - log.debug: "All changes staged successfully" - - - var.set: - commitSha: "${#git.commit(sourceDir, commitMessage)}" - on.fail: - - log.warn: - msg: "Failed to commit staged changes with message: '${commitMessage}'" - cause: ${lastException} - - throw: ${lastException} - - log.info: "Successfully committed changes: ${commitSha}" - - # Pre-push diagnostics: Check for available credentials - - var.set: - githubToken: "${#env('GITHUB_TOKEN')?:#env('GH_TOKEN')}" - gitlabToken: "${#env('CI_JOB_TOKEN')}" - azureToken: "${#env('SYSTEM_ACCESSTOKEN')}" - bitbucketToken: "${#env('BITBUCKET_TOKEN')}" - - - log.debug: "Credential availability check - GitHub: ${githubToken!=null?'configured':'not found'}, GitLab: ${gitlabToken!=null?'configured':'not found'}, Azure: ${azureToken!=null?'configured':'not found'}, Bitbucket: ${bitbucketToken!=null?'configured':'not found'}" - - - if: "${githubToken==null && gitlabToken==null && azureToken==null && bitbucketToken==null && gitRepoInfo.repository.remoteUrl!=null && gitRepoInfo.repository.remoteUrl.contains('https')}" - do: - - log.warn: "WARNING: No CI credentials detected (GITHUB_TOKEN/GH_TOKEN, CI_JOB_TOKEN, SYSTEM_ACCESSTOKEN, BITBUCKET_TOKEN). Remote URL uses HTTPS and requires authentication: ${gitRepoInfo.repository.remoteUrl}" - - # Push branch to remote with detailed error context - - var.set: - pushedRef: ${#git.push(sourceDir, branchName)} - on.fail: - - var.set: - errorMsg: "Failed to push branch '${branchName}' to remote" - remoteUrl: "${gitRepoInfo.repository.remoteUrl?:'unknown'}" - exceptionType: "${lastException.type}" - exceptionMsg: "${lastException.message}" - - log.warn: - msg: "${errorMsg}. Remote: ${remoteUrl}. Error: ${exceptionType}: ${exceptionMsg}" - cause: ${lastException} - - if: "${exceptionMsg.contains('No credentials available')}" - do: - - log.warn: "DIAGNOSIS: Push failed due to missing credentials. Required environment variables for HTTPS remotes: GITHUB_TOKEN or GH_TOKEN (GitHub), CI_JOB_TOKEN (GitLab), SYSTEM_ACCESSTOKEN (Azure DevOps), or BITBUCKET_TOKEN (Bitbucket)" - - log.warn: "DIAGNOSIS: Ensure the CI runner has permission to set and pass through authentication tokens to this action" - - throw: - msg: "${errorMsg}" - cause: ${lastException} - - log.info: "Successfully pushed branch to remote: ${pushedRef}" - - # Create PR/MR based on detected CI system - - var.set: - ci.detected: ${#_ci.detect()} - ci.type: ${ci.detected.type} - baseBranch: "${#env('BASE_BRANCH')?:'main'}" - - log.debug: "Detected CI system: ${ci.type}" - - log.debug: "Detected Base Branch: ${baseBranch}" - - # GitHub PR creation - - if: ${ci.type=='github'} - do: - - var.set: - repoOwner: "${#git.ciRepositoryOwner()}" - prHead: "${repoOwner}:${branchName}" - - log.debug: "Creating GitHub PR: head=${prHead}, base=${baseBranch}, title=${prTitle}" - - var.set: - pr: ${#_ci.detect().repo().createPullRequest(prTitle, prHead, baseBranch, prBody)} - on.fail: - - log.warn: - msg: "Failed to create GitHub Pull Request from '${branchName}' to '${baseBranch}'" - cause: ${lastException} - - log.warn: "Troubleshooting: Verify branch '${branchName}' exists on remote, and base branch '${baseBranch}' is valid" - - throw: ${lastException} - - log.info: "Successfully created GitHub Pull Request #${pr.number}: ${pr.html_url}" - - # GitLab MR creation - - if: ${ci.type=='gitlab'} - do: - - var.set: - mr: ${#_ci.detect().project().createMergeRequest(prTitle, branchName, baseBranch, prBody)} - on.fail: - - log.warn: - msg: "Failed to create GitLab Merge Request from '${branchName}' to '${baseBranch}'" - cause: ${lastException} - - throw: ${lastException} - - log.info: "Successfully created GitLab Merge Request !${mr.iid}: ${mr.web_url}" - - # Unknown CI system - log warning - - if: ${ci.type!='github' && ci.type!='gitlab'} - log.warn: "PR/MR creation not supported for CI system '${ci.type}'. Branch '${branchName}' (${pushedRef}) has been pushed; please create a PR/MR manually." - - # Success summary - - log.info: "PR creation workflow completed successfully. Branch '${branchName}' pushed to remote." diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/apply-remediations.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/apply-remediations.yaml deleted file mode 100644 index 8953b54c5dd..00000000000 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/apply-remediations.yaml +++ /dev/null @@ -1,47 +0,0 @@ -# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json -author: Fortify -usage: - header: Apply Aviator remediations - description: | - This action applies Aviator auto-remediation fixes to source code for a given - SSC application version. It downloads the audited FPR with remediation suggestions - and applies the fixes to the local source tree. - - This action requires: - - An SSC session to be active (for downloading the audited FPR with remediations) - - An Aviator session to be active (for the apply-remediations command) - - After running this action, any modified files can be committed and pushed using - the `create-pr` action. - -cli.options: - aviatorArtifact: - names: --aviator-artifact - description: "Artifact ID to apply remediations from. If not specified, uses --latest to apply from the most recent Aviator-processed artifact." - required: false - -config: - output: immediate - rest.target.default: ssc - mcp: exclude - -steps: - - run.fcli: - detect-env: - group: detect-env - cmd: fcli action run detect-env - status.check: true - - - var.set: - av: ${#env('SSC_APPVERSION')?:global.ci.defaultFortifyRepo} - - if: ${av==null} - throw: SSC application version must be specified through SSC_APPVERSION environment variable - - - var.set: - sourceDir: ${global.ci.sourceDir?:'.'} - artifactSelector: "${#isNotBlank(cli.aviatorArtifact) ? '--artifact-id '+cli.aviatorArtifact : '--av '+av+' --latest'}" - - - run.fcli: - APPLY_REMEDIATIONS: - cmd: "aviator ssc apply-remediations --source-dir \"${sourceDir}\" ${artifactSelector}" - status.check: true diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml index 967e6904b84..30f23618eaa 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml @@ -208,18 +208,29 @@ steps: - "${#isBlank(aviator.artifactId) ? 'No artifact produced by Aviator audit, nothing to wait for' : ''}" APPLY_REMEDIATIONS: - cmd: ${#actionCmd('APPLY_REMEDIATIONS', 'ssc', 'apply-remediations')} --aviator-artifact ${aviator.artifactId} "--progress=none" + cmd: ${#fcliCmd('APPLY_REMEDIATIONS', 'aviator ssc apply-remediations')} "--rel=${global.ci.rel}" "--source-dir=${global.ci.sourceDir}" "--progress=none" skip.if-reason: - - ${#actionCmdSkipFromEnvReason('APPLY_REMEDIATIONS', true)} # Skip unless DO_APPLY_REMEDIATIONS==true or APPLY_REMEDIATIONS_ACTION/EXTRA_OPTS defined + - ${#fcliCmdSkipFromEnvReason('APPLY_REMEDIATIONS', true)} # Skip unless DO_APPLY_REMEDIATIONS==true or APPLY_REMEDIATIONS_ACTION/EXTRA_OPTS defined - ${postScan.skipReason} # Skip if no scans were run - ${aviator.skipReason} # Skip if Aviator is not configured - ${AVIATOR_WAIT.dependencySkipReason} # Skip if AVIATOR_WAIT was skipped or failed (no remediations available) + on.success: + - var.set: { remediationsApplied: 'true' } CREATE_PR: - cmd: "${#actionCmd('CREATE_PR', 'ssc', 'create-pr')} --progress=none" + cmd: ${#actionCmd('CREATE_PR', '', 'action run commit-and-create-pr')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" skip.if-reason: - ${#actionCmdSkipFromEnvReason('CREATE_PR', true)} # Skip unless DO_CREATE_PR==true or CREATE_PR_EXTRA_OPTS defined - - ${APPLY_REMEDIATIONS.dependencySkipReason} # Skip if APPLY_REMEDIATIONS was skipped or failed + - ${!remediationsApplied?'Apply remediations was skipped or failed, skipping PR creation':''} + on.success: + - if: ${CREATE_PR.variables.status=='success'} + log.info: "Pull/Merge Request successfully created: ${CREATE_PR.variables.prLink}" + - if: ${CREATE_PR.variables.status=='no_changes'} + log.info: "No changes detected - PR/MR creation skipped" + - if: ${CREATE_PR.variables.status=='no_pr_support'} + log.warn: "Branch created and pushed, but PR/MR creation not supported for ${CREATE_PR.variables.ciSystem}" + - if: ${CREATE_PR.variables.status=='error'} + log.warn: "PR/MR creation failed: ${CREATE_PR.variables.errorMessage}" CHECK_POLICY: cmd: ${#actionCmd('CHECK_POLICY', 'ssc', 'check-policy')} "--av=${global.ci.av}" "--progress=none" @@ -301,3 +312,5 @@ formatters: ${APPVERSION_SUMMARY.stdout} + + diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml deleted file mode 100644 index 7bfe66be395..00000000000 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml +++ /dev/null @@ -1,177 +0,0 @@ -# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json -author: Fortify -usage: - header: Create a pull request from changed files - description: | - This action detects local file changes, creates a new branch, commits the changes, - pushes to the remote, and raises a pull/merge request via the detected CI system's API. - - This action requires: - - Git repository access (for branch/commit/push operations) - - CI system token with PR/MR creation permissions (GITHUB_TOKEN, CI_JOB_TOKEN, etc.) - - Configuration via environment variables: - - `SOURCE_DIR` — Source directory where changes are detected (default: CI workspace or current directory) - - `PR_TITLE` — PR/MR title (default: "fix: Fortify auto-remediation fixes [generated by fcli]") - - `PR_BODY` — PR/MR body (default: auto-generated description) - - `PR_BRANCH_PREFIX` — Branch name prefix (default: "fcli/remediation") - - `PR_COMMIT_MESSAGE` — Commit message (default: "fix: apply Fortify auto-remediation fixes [generated by fcli]") - -config: - output: immediate - rest.target.default: ssc - run.fcli.status.log.default: true # By default, we log all exit statuses - run.fcli.status.check.default: true - mcp: exclude - -steps: - - run.fcli: - detect-env: - group: detect-env - cmd: fcli action run detect-env - status.check: true - on.fail: - - log.warn: - msg: "CI environment detection failed" - cause: ${detect-env_exception} - - - var.set: - sourceDir: ${#env('SOURCE_DIR')?:global.ci.sourceDir?:'.'} - - # Log initial git repository information for diagnostics - - var.set: - gitRepoInfo: ${#git.localRepo(sourceDir)} - - if: ${gitRepoInfo==null} - throw: "Source directory '${sourceDir}' is not a git repository. Cannot detect repository information needed for PR creation." - - if: ${#isBlank(gitRepoInfo.repository.remoteUrl)} - throw: "Git repository has no remote URL configured. Cannot create PR without remote repository access." - - if: ${gitRepoInfo!=null} - log.info: "Git repository: workspace=${gitRepoInfo.repository.workspaceDir}, remote=${gitRepoInfo.repository.remoteUrl?:'not configured'}, branch=${gitRepoInfo.branch.short_?:'detached HEAD'}" - - # Log current commit information for reference (debug level) - - if: ${gitRepoInfo.commit!=null} - log.debug: "Current HEAD: ${gitRepoInfo.commit.headId.short} - ${gitRepoInfo.commit.message.short}" - - # Check if there are changes to commit - - var.set: - hasChanges: ${#git.hasChanges(sourceDir)} - - - if: ${!hasChanges} - do: - - log.info: "No changes detected in ${sourceDir}, skipping PR creation." - - exit: 0 - - - log.info: "Changes detected in ${sourceDir}, proceeding with PR creation." - - - var.set: - branchPrefix: ${#env('PR_BRANCH_PREFIX')?:'aviator/remediations'} - commitMessage: "${#env('PR_COMMIT_MESSAGE')?:'fix: apply Fortify auto-remediation fixes [generated by fcli]'}" - prTitle: "${#env('PR_TITLE')?:'fix: Fortify auto-remediation fixes'}" - prBody: "${#env('PR_BODY')?:'This pull request contains auto-remediation fixes generated by Fortify SAST Aviator.'}" - - # Create a new branch for the changes with error handling - - var.set: - branchName: ${#git.createBranch(sourceDir, branchPrefix)} - on.fail: - - log.warn: - msg: "Failed to create branch with prefix '${branchPrefix}'" - cause: ${lastException} - - throw: ${lastException} - - log.info: "Successfully created branch: ${branchName}" - - # Stage and commit changes with error handling - - var.set: - staged: ${#git.addAll(sourceDir)} - on.fail: - - log.warn: - msg: "Failed to stage changes in ${sourceDir}" - cause: ${lastException} - - throw: ${lastException} - - log.debug: "All changes staged successfully" - - - var.set: - commitSha: "${#git.commit(sourceDir, commitMessage)}" - on.fail: - - log.warn: - msg: "Failed to commit staged changes with message: '${commitMessage}'" - cause: ${lastException} - - throw: ${lastException} - - log.info: "Successfully committed changes: ${commitSha}" - - # Pre-push diagnostics: Check for available credentials - - var.set: - githubToken: "${#env('GITHUB_TOKEN')?:#env('GH_TOKEN')}" - gitlabToken: "${#env('CI_JOB_TOKEN')}" - azureToken: "${#env('SYSTEM_ACCESSTOKEN')}" - bitbucketToken: "${#env('BITBUCKET_TOKEN')}" - - - log.debug: "Credential availability check - GitHub: ${githubToken!=null?'configured':'not found'}, GitLab: ${gitlabToken!=null?'configured':'not found'}, Azure: ${azureToken!=null?'configured':'not found'}, Bitbucket: ${bitbucketToken!=null?'configured':'not found'}" - - - if: "${githubToken==null && gitlabToken==null && azureToken==null && bitbucketToken==null && gitRepoInfo.repository.remoteUrl!=null && gitRepoInfo.repository.remoteUrl.contains('https')}" - do: - - log.warn: "WARNING: No CI credentials detected (GITHUB_TOKEN/GH_TOKEN, CI_JOB_TOKEN, SYSTEM_ACCESSTOKEN, BITBUCKET_TOKEN). Remote URL uses HTTPS and requires authentication: ${gitRepoInfo.repository.remoteUrl}" - - # Push branch to remote with detailed error context - - var.set: - pushedRef: ${#git.push(sourceDir, branchName)} - on.fail: - - var.set: - errorMsg: "Failed to push branch '${branchName}' to remote" - remoteUrl: "${gitRepoInfo.repository.remoteUrl?:'unknown'}" - exceptionType: "${lastException.type}" - exceptionMsg: "${lastException.message}" - - log.warn: - msg: "${errorMsg}. Remote: ${remoteUrl}. Error: ${exceptionType}: ${exceptionMsg}" - cause: ${lastException} - - if: "${exceptionMsg.contains('No credentials available')}" - do: - - log.warn: "DIAGNOSIS: Push failed due to missing credentials. Required environment variables for HTTPS remotes: GITHUB_TOKEN or GH_TOKEN (GitHub), CI_JOB_TOKEN (GitLab), SYSTEM_ACCESSTOKEN (Azure DevOps), or BITBUCKET_TOKEN (Bitbucket)" - - log.warn: "DIAGNOSIS: Ensure the CI runner has permission to set and pass through authentication tokens to this action" - - throw: - msg: "${errorMsg}" - cause: ${lastException} - - log.info: "Successfully pushed branch to remote: ${pushedRef}" - - # Create PR/MR based on detected CI system - - var.set: - ci.detected: ${#_ci.detect()} - ci.type: ${ci.detected.type} - baseBranch: "${#env('BASE_BRANCH')?:'main'}" - - log.debug: "Detected CI system: ${ci.type}" - - log.debug: "Detected Base Branch: ${baseBranch}" - - # GitHub PR creation - - if: ${ci.type=='github'} - do: - - var.set: - repoOwner: "${#git.ciRepositoryOwner()}" - prHead: "${repoOwner}:${branchName}" - - log.debug: "Creating GitHub PR: head=${prHead}, base=${baseBranch}, title=${prTitle}" - - var.set: - pr: ${#_ci.detect().repo().createPullRequest(prTitle, prHead, baseBranch, prBody)} - on.fail: - - log.warn: - msg: "Failed to create GitHub Pull Request from '${branchName}' to '${baseBranch}'" - cause: ${lastException} - - log.warn: "Troubleshooting: Verify branch '${branchName}' exists on remote, and base branch '${baseBranch}' is valid" - - throw: ${lastException} - - log.info: "Successfully created GitHub Pull Request #${pr.number}: ${pr.html_url}" - - # GitLab MR creation - - if: ${ci.type=='gitlab'} - do: - - var.set: - mr: ${#_ci.detect().project().createMergeRequest(prTitle, branchName, baseBranch, prBody)} - on.fail: - - log.warn: - msg: "Failed to create GitLab Merge Request from '${branchName}' to '${baseBranch}'" - cause: ${lastException} - - throw: ${lastException} - - log.info: "Successfully created GitLab Merge Request !${mr.iid}: ${mr.web_url}" - - # Unknown CI system - log warning - - if: ${ci.type!='github' && ci.type!='gitlab'} - log.warn: "PR/MR creation not supported for CI system '${ci.type}'. Branch '${branchName}' (${pushedRef}) has been pushed; please create a PR/MR manually." - - # Success summary - - log.info: "PR creation workflow completed successfully. Branch '${branchName}' pushed to remote." From d4dc0596dc0e47b58f722b0abf507b97b30f57b1 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Fri, 19 Jun 2026 18:48:58 +0530 Subject: [PATCH 24/46] fixed compilation issue due to UTF-8 BOM --- .../fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml index 54929c87168..dbd9f815c87 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json +# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json author: Fortify usage: header: Create a pull request or merge request on GitHub or GitLab From 27c7ad8f3f72fac70cd41cfc8eebc6b7688aa5ed Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Fri, 19 Jun 2026 19:06:53 +0530 Subject: [PATCH 25/46] fix compilation issue --- .../cli/generic_action/actions/zip/commit-and-create-pr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml index 9b5eab8941f..ad7db0b251d 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json +# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json author: Fortify usage: header: Stage, commit, push and create a pull/merge request From 5829607345b889332d74a8262bf5c6accd74a1e7 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Fri, 19 Jun 2026 19:27:02 +0530 Subject: [PATCH 26/46] fixed the action as per the schema --- .../actions/zip/commit-and-create-pr.yaml | 108 ++++++++++-------- .../actions/zip/git-stage-commit-push.yaml | 108 +++++++++--------- .../actions/zip/pr-or-mr-create.yaml | 103 +++++++++-------- 3 files changed, 177 insertions(+), 142 deletions(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml index ad7db0b251d..45d78825c8b 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml @@ -15,60 +15,76 @@ usage: SYSTEM_ACCESSTOKEN (Azure DevOps), or BITBUCKET_TOKEN (Bitbucket) Configuration via CLI options or environment variables: - - `--source-dir` / `SOURCE_DIR` — Directory where changes are detected (default: '.') - - `--branch-prefix` / `BRANCH_PREFIX` — Branch name prefix (default: "fcli/aviator-remediations") - - `--title` / `PR_TITLE` — PR/MR title (default: "fix: Fortify auto-remediation fixes [Generated by fcli]") - - `--body` / `PR_BODY` — PR/MR description (default: generic message) - - `--base-branch` / `BASE_BRANCH` — Target branch for the PR/MR (default: auto-detected from remote, fallback "main") - - `--author-name` / `GIT_AUTHOR_NAME` — Commit author name (default: "fcli-actions[bot]") - - `--author-email` / `GIT_AUTHOR_EMAIL` — Commit author email (default: "fcli-actions@opentext.com") - -options: - - name: source-dir - description: Directory where changes are detected - defaultValue: "${#env('SOURCE_DIR')?:global.ci.sourceDir?:'.'}" - - - name: branch-prefix - description: Prefix for the new branch name - defaultValue: "${#env('BRANCH_PREFIX')?:'fcli/aviator-remediations'}" - - - name: title - description: PR/MR title - defaultValue: "${#env('PR_TITLE')?:'fix: Fortify auto-remediation fixes [Generated by fcli aviator]'}" - - - name: body - description: PR/MR description - defaultValue: "${#env('PR_BODY')?:'This pull request contains changes applied by fcli aviator.'}" - - - name: author-name - description: Git author name for the commit - defaultValue: "${#env('GIT_AUTHOR_NAME')?:'fcli-aviator[bot]'}" - - - name: author-email - description: Git author email for the commit - defaultValue: "${#env('GIT_AUTHOR_EMAIL')?:'fcli-aviator@opentext.com'}" + - `--source-dir` / `SOURCE_DIR` -- Directory where changes are detected (default: '.') + - `--branch-prefix` / `BRANCH_PREFIX` -- Branch name prefix (default: "fcli/aviator-remediations") + - `--title` / `PR_TITLE` -- PR/MR title + - `--body` / `PR_BODY` -- PR/MR description + - `--base-branch` / `BASE_BRANCH` -- Target branch for the PR/MR (default: auto-detected from remote, fallback "main") + - `--author-name` / `GIT_AUTHOR_NAME` -- Commit author name (default: "fcli-aviator[bot]") + - `--author-email` / `GIT_AUTHOR_EMAIL` -- Commit author email config: output: immediate +cli.options: + sourceDir: + names: --source-dir, -s + description: >- + Directory where changes are detected. + Defaults to current working directory if not specified. + required: false + default: "${#env('SOURCE_DIR')?:'.'}" + branchPrefix: + names: --branch-prefix + description: >- + Prefix for the new branch name. + required: false + default: "${#env('BRANCH_PREFIX')?:'fcli/aviator-remediations'}" + title: + names: --title, -t + description: >- + PR/MR title. + required: false + default: "${#env('PR_TITLE')?:'fix: Fortify auto-remediation fixes [Generated by fcli aviator]'}" + body: + names: --body + description: >- + PR/MR description. + required: false + default: "${#env('PR_BODY')?:'This pull request contains changes applied by fcli aviator.'}" + baseBranch: + names: --base-branch + description: >- + Target branch for the PR/MR. Auto-detected from the remote HEAD if not specified, + falling back to "main". + required: false + default: "${#env('BASE_BRANCH')?:''}" + authorName: + names: --author-name + description: >- + Git author name for the commit. + required: false + default: "${#env('GIT_AUTHOR_NAME')?:'fcli-aviator[bot]'}" + authorEmail: + names: --author-email + description: >- + Git author email for the commit. + required: false + default: "${#env('GIT_AUTHOR_EMAIL')?:'fcli-aviator@opentext.com'}" + steps: + # Compute base branch: CLI option/env var -> git remote HEAD -> "main" - var.set: - sourceDir: "${#opt('source-dir', global.ci.sourceDir?:'.')}" - branchPrefix: "${#opt('branch-prefix', 'fcli/aviator-remediations')}" - prTitle: "${#opt('title', 'fix: Fortify auto-remediation fixes [Generated by fcli aviator]')}" - prBody: "${#opt('body', 'This pull request contains changes applied by fcli aviator.')}" - baseBranch: "${#ifBlank(#opt('base-branch', ''), #ifBlank(#git.defaultBranch(sourceDir), 'main'))}" - authorName: "${#opt('author-name', 'fcli-aviator[bot]')}" - authorEmail: "${#opt('author-email', 'fcli-aviator@opentext.com')}" + baseBranch: "${#ifBlank(cli.baseBranch, #ifBlank(#git.defaultBranch(cli.sourceDir), 'main'))}" # Step 1: Stage, commit and push - action.run: action: git-stage-commit-push options: - source-dir: "${sourceDir}" - branch-prefix: "${branchPrefix}" - author-name: "${authorName}" - author-email: "${authorEmail}" + source-dir: "${cli.sourceDir}" + branch-prefix: "${cli.branchPrefix}" + author-name: "${cli.authorName}" + author-email: "${cli.authorEmail}" - var.set: gitResult: ${result} @@ -81,11 +97,11 @@ steps: - action.run: action: pr-or-mr-create options: - source-dir: "${sourceDir}" + source-dir: "${cli.sourceDir}" source-branch: "${gitResult.branchName}" base-branch: "${baseBranch}" - title: "${prTitle}" - body: "${prBody}" + title: "${cli.title}" + body: "${cli.body}" - var.set: prResult: ${result} diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml index 4737338c215..5288d80f85a 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml @@ -14,67 +14,73 @@ usage: SYSTEM_ACCESSTOKEN, or BITBUCKET_TOKEN) Configuration via CLI options or environment variables: - - `--source-dir` / `SOURCE_DIR` — Directory where changes are detected (default: '.') - - `--branch-prefix` / `BRANCH_PREFIX` — Branch name prefix (default: "fcli/aviator-remediations") - - `--commit-message` / `COMMIT_MESSAGE` — Commit message (default: "fix: apply automated fixes [generated by fcli aviator]") - - `--author-name` / `GIT_AUTHOR_NAME` — Commit author name (default: "fcli-aviator[bot]") - - `--author-email` / `GIT_AUTHOR_EMAIL` — Commit author email (default: "fcli-aviator@opentext.com") + - `--source-dir` / `SOURCE_DIR` -- Directory where changes are detected (default: '.') + - `--branch-prefix` / `BRANCH_PREFIX` -- Branch name prefix (default: "fcli/aviator-remediations") + - `--commit-message` / `COMMIT_MESSAGE` -- Commit message + - `--author-name` / `GIT_AUTHOR_NAME` -- Commit author name (default: "fcli-aviator[bot]") + - `--author-email` / `GIT_AUTHOR_EMAIL` -- Commit author email Output variables (accessible by subsequent actions or scripts): - - `result.status` — "success", "no_changes", or "error" - - `result.branchName` — Name of the newly created branch - - `result.commitSha` — SHA of the created commit - - `result.pushedRef` — Full ref pushed to the remote - -options: - - name: source-dir - description: Directory where changes are detected - defaultValue: "${#env('SOURCE_DIR')?:global.ci.sourceDir?:'.'}" - - - name: branch-prefix - description: Prefix for the new branch name - defaultValue: "${#env('BRANCH_PREFIX')?:'fcli/aviator-remediations'}" - - - name: commit-message - description: Git commit message - defaultValue: "${#env('COMMIT_MESSAGE')?:'fix: apply automated fixes [generated by fcli aviator]'}" - - - name: author-name - description: Git author name for the commit - defaultValue: "${#env('GIT_AUTHOR_NAME')?:'fcli-aviator[bot]'}" - - - name: author-email - description: Git author email for the commit - defaultValue: "${#env('GIT_AUTHOR_EMAIL')?:'fcli-aviator@opentext.com'}" + - `result.status` -- "success", "no_changes", or "error" + - `result.branchName` -- Name of the newly created branch + - `result.commitSha` -- SHA of the created commit + - `result.pushedRef` -- Full ref pushed to the remote config: output: immediate -steps: - - var.set: - sourceDir: "${#opt('source-dir', global.ci.sourceDir?:'.')}" - branchPrefix: "${#opt('branch-prefix', 'fcli/aviator-remediations')}" - commitMessage: "${#opt('commit-message', 'fix: apply automated fixes [generated by fcli aviator]')}" - authorName: "${#opt('author-name', 'fcli-aviator[bot]')}" - authorEmail: "${#opt('author-email', 'fcli-aviator@opentext.com')}" +# NOTE: When updating any options, commit-and-create-pr action may also need to be updated +cli.options: + sourceDir: + names: --source-dir, -s + description: >- + Directory where changes are detected. + Defaults to current working directory if not specified. + required: false + default: "${#env('SOURCE_DIR')?:'.'}" + branchPrefix: + names: --branch-prefix + description: >- + Prefix for the new branch name. The branch name will be /. + required: false + default: "${#env('BRANCH_PREFIX')?:'fcli/aviator-remediations'}" + commitMessage: + names: --commit-message, -m + description: >- + Git commit message. + required: false + default: "${#env('COMMIT_MESSAGE')?:'fix: apply automated fixes [generated by fcli aviator]'}" + authorName: + names: --author-name + description: >- + Git author name for the commit. + required: false + default: "${#env('GIT_AUTHOR_NAME')?:'fcli-aviator[bot]'}" + authorEmail: + names: --author-email + description: >- + Git author email for the commit. + required: false + default: "${#env('GIT_AUTHOR_EMAIL')?:'fcli-aviator@opentext.com'}" - - log.debug: "Action configuration: sourceDir=${sourceDir}, branchPrefix=${branchPrefix}, author=${authorName} <${authorEmail}>" +steps: + - log.debug: "Action configuration: sourceDir=${cli.sourceDir}, branchPrefix=${cli.branchPrefix}, author=${cli.authorName} <${cli.authorEmail}>" # Validate git repository - var.set: - gitRepoInfo: ${#git.localRepo(sourceDir)} + gitRepoInfo: ${#git.localRepo(cli.sourceDir)} - if: ${gitRepoInfo==null} - throw: "Source directory '${sourceDir}' is not a git repository." + throw: "Source directory '${cli.sourceDir}' is not a git repository." - if: "${#isBlank(gitRepoInfo.repository.remoteUrl)}" throw: "Git repository has no remote URL configured. A remote is required for push." - log.info: "Git repository: remote=${gitRepoInfo.repository.remoteUrl}, branch=${gitRepoInfo.branch.short?:'detached HEAD'}" # Check for changes - var.set: - hasChanges: ${#git.hasChanges(sourceDir)} + hasChanges: ${#git.hasChanges(cli.sourceDir)} - if: ${!hasChanges} do: - - log.info: "No changes detected in ${sourceDir}, skipping commit and push." + - log.info: "No changes detected in ${cli.sourceDir}, skipping commit and push." - var.set: result: ${T(java.util.LinkedHashMap).new()} result.status: "no_changes" @@ -82,37 +88,37 @@ steps: result.commitSha: "" result.pushedRef: "" - exit: 0 - - log.info: "Changes detected in ${sourceDir}, proceeding." + - log.info: "Changes detected in ${cli.sourceDir}, proceeding." # Set git author identity - var.set: - gitAuthorEnv: ${#env.set('GIT_AUTHOR_NAME', authorName).put('GIT_AUTHOR_EMAIL', authorEmail)} + gitAuthorEnv: ${#env.set('GIT_AUTHOR_NAME', cli.authorName).put('GIT_AUTHOR_EMAIL', cli.authorEmail)} on.fail: - log.warn: "Warning: Unable to set git author environment variables" # Create branch - var.set: - branchName: ${#git.createBranch(sourceDir, branchPrefix)} + branchName: ${#git.createBranch(cli.sourceDir, cli.branchPrefix)} on.fail: - log.warn: - msg: "Failed to create branch with prefix '${branchPrefix}'" + msg: "Failed to create branch with prefix '${cli.branchPrefix}'" cause: ${lastException} - throw: ${lastException} - log.info: "Created branch: ${branchName}" # Stage all changes - var.set: - staged: ${#git.addAll(sourceDir)} + staged: ${#git.addAll(cli.sourceDir)} on.fail: - log.warn: - msg: "Failed to stage changes in ${sourceDir}" + msg: "Failed to stage changes in ${cli.sourceDir}" cause: ${lastException} - throw: ${lastException} - log.debug: "All changes staged successfully" # Commit - var.set: - commitSha: "${#git.commit(sourceDir, commitMessage)}" + commitSha: "${#git.commit(cli.sourceDir, cli.commitMessage)}" on.fail: - log.warn: msg: "Failed to commit staged changes" @@ -122,7 +128,7 @@ steps: # Push - var.set: - pushedRef: ${#git.push(sourceDir, branchName)} + pushedRef: ${#git.push(cli.sourceDir, branchName)} on.fail: - var.set: remoteUrl: "${gitRepoInfo.repository.remoteUrl?:'unknown'}" @@ -143,4 +149,4 @@ steps: result.branchName: "${branchName}" result.commitSha: "${commitSha}" result.pushedRef: "${pushedRef}" - - log.info: "git-stage-commit-push completed: branch=${result.branchName}, commit=${result.commitSha}" + - log.info: "git-stage-commit-push completed: branch=${result.branchName}, commit=${result.commitSha}" \ No newline at end of file diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml index dbd9f815c87..18e560c185c 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml @@ -15,54 +15,67 @@ usage: - A CI token with PR/MR creation permissions (GITHUB_TOKEN/GH_TOKEN or CI_JOB_TOKEN) Configuration via CLI options or environment variables: - - `--source-dir` / `SOURCE_DIR` — Local repo directory used to detect the platform (default: '.') - - `--source-branch` / `SOURCE_BRANCH` — Branch to raise the PR/MR from (required) - - `--base-branch` / `BASE_BRANCH` — Target branch for the PR/MR (default: auto-detected from remote, fallback "main") - - `--title` / `PR_TITLE` — PR/MR title (default: "fix: Fortify auto-remediation fixes") - - `--body` / `PR_BODY` — PR/MR description (default: generic message) + - `--source-dir` / `SOURCE_DIR` -- Local repo directory used to detect the platform (default: '.') + - `--source-branch` / `SOURCE_BRANCH` -- Branch to raise the PR/MR from (required) + - `--base-branch` / `BASE_BRANCH` -- Target branch for the PR/MR (default: auto-detected from remote, fallback "main") + - `--title` / `PR_TITLE` -- PR/MR title + - `--body` / `PR_BODY` -- PR/MR description Output variables: - - `result.status` — "success", "no_pr_support", or "error" - - `result.prLink` — URL of the created PR/MR (empty on failure) - - `result.platform` — Detected repository platform ("github", "gitlab", or "unknown") - -options: - - name: source-dir - description: Local repo directory (used to detect the repository platform) - defaultValue: "${#env('SOURCE_DIR')?:global.ci.sourceDir?:'.'}" - - - name: source-branch - description: Source branch to raise the PR/MR from - required: true - defaultValue: "${#env('SOURCE_BRANCH')?:''}" - - - name: title - description: PR/MR title - defaultValue: "${#env('PR_TITLE')?:'fix: Fortify auto-remediation fixes [Generated by fcli aviator]'}" - - - name: body - description: PR/MR description - defaultValue: "${#env('PR_BODY')?:'This pull request contains changes applied by fcli aviator.'}" + - `result.status` -- "success", "no_pr_support", or "error" + - `result.prLink` -- URL of the created PR/MR (empty on failure) + - `result.platform` -- Detected repository platform ("github", "gitlab", or "unknown") config: output: immediate +cli.options: + sourceDir: + names: --source-dir, -s + description: >- + Local repo directory used to detect the repository platform. + Defaults to current working directory if not specified. + required: false + default: "${#env('SOURCE_DIR')?:'.'}" + sourceBranch: + names: --source-branch + description: >- + Source branch to raise the PR/MR from. + required: false + default: "${#env('SOURCE_BRANCH')?:''}" + baseBranch: + names: --base-branch + description: >- + Target branch for the PR/MR. Auto-detected from the remote HEAD if not specified, + falling back to "main". + required: false + default: "${#env('BASE_BRANCH')?:''}" + title: + names: --title, -t + description: >- + PR/MR title. + required: false + default: "${#env('PR_TITLE')?:'fix: Fortify auto-remediation fixes [Generated by fcli aviator]'}" + body: + names: --body + description: >- + PR/MR description. + required: false + default: "${#env('PR_BODY')?:'This pull request contains changes applied by fcli aviator.'}" + steps: + # Compute base branch: CLI option/env var -> git remote HEAD -> "main" - var.set: - sourceDir: "${#opt('source-dir', global.ci.sourceDir?:'.')}" - sourceBranch: "${#opt('source-branch', '')}" - baseBranch: "${#ifBlank(#opt('base-branch', ''), #ifBlank(#git.defaultBranch(sourceDir), 'main'))}" - prTitle: "${#opt('title', 'fix: Fortify auto-remediation fixes [Generated by fcli aviator]')}" - prBody: "${#opt('body', 'This pull request contains changes applied by fcli aviator.')}" + baseBranch: "${#ifBlank(cli.baseBranch, #ifBlank(#git.defaultBranch(cli.sourceDir), 'main'))}" - - if: "${#isBlank(sourceBranch)}" + - if: "${#isBlank(cli.sourceBranch)}" throw: "Option '--source-branch' is required. Pass the branch created by git-stage-commit-push." - - log.debug: "PR/MR configuration: platform detection from '${sourceDir}', source=${sourceBranch}, base=${baseBranch}" + - log.debug: "PR/MR configuration: platform detection from '${cli.sourceDir}', source=${cli.sourceBranch}, base=${baseBranch}" # Detect repository platform from git remote URL - var.set: - repoPlatform: ${#git.repositoryPlatform(sourceDir)} + repoPlatform: ${#git.repositoryPlatform(cli.sourceDir)} - log.debug: "Detected repository platform: ${repoPlatform}" - var.set: @@ -76,15 +89,15 @@ steps: do: - var.set: repoOwner: "${#git.ciRepositoryOwner()}" - prHead: "${repoOwner}:${sourceBranch}" - - log.debug: "Creating GitHub PR: head=${prHead}, base=${baseBranch}, title=${prTitle}" + prHead: "${repoOwner}:${cli.sourceBranch}" + - log.debug: "Creating GitHub PR: head=${prHead}, base=${baseBranch}, title=${cli.title}" - var.set: - pr: ${#_ci.detect().repo().createPullRequest(prTitle, prHead, baseBranch, prBody)} + pr: ${#_ci.detect().repo().createPullRequest(cli.title, prHead, baseBranch, cli.body)} on.fail: - log.warn: - msg: "Failed to create GitHub Pull Request from '${sourceBranch}' to '${baseBranch}'" + msg: "Failed to create GitHub Pull Request from '${cli.sourceBranch}' to '${baseBranch}'" cause: ${lastException} - - log.warn: "Troubleshooting: Verify branch '${sourceBranch}' exists on remote and base branch '${baseBranch}' is valid" + - log.warn: "Troubleshooting: Verify branch '${cli.sourceBranch}' exists on remote and base branch '${baseBranch}' is valid" - var.set: result.status: "error" result.errorMessage: "${lastException.message}" @@ -98,14 +111,14 @@ steps: # GitLab MR creation - if: "${repoPlatform=='gitlab'}" do: - - log.debug: "Creating GitLab Merge Request: source=${sourceBranch}, target=${baseBranch}, title=${prTitle}" + - log.debug: "Creating GitLab Merge Request: source=${cli.sourceBranch}, target=${baseBranch}, title=${cli.title}" - var.set: - mr: ${#_ci.detect().project().createMergeRequest(prTitle, sourceBranch, baseBranch, prBody)} + mr: ${#_ci.detect().project().createMergeRequest(cli.title, cli.sourceBranch, baseBranch, cli.body)} on.fail: - log.warn: - msg: "Failed to create GitLab Merge Request from '${sourceBranch}' to '${baseBranch}'" + msg: "Failed to create GitLab Merge Request from '${cli.sourceBranch}' to '${baseBranch}'" cause: ${lastException} - - log.warn: "Troubleshooting: Verify branch '${sourceBranch}' exists on remote and base branch '${baseBranch}' is valid" + - log.warn: "Troubleshooting: Verify branch '${cli.sourceBranch}' exists on remote and base branch '${baseBranch}' is valid" - var.set: result.status: "error" result.errorMessage: "${lastException.message}" @@ -119,10 +132,10 @@ steps: # Unsupported repository platform - if: "${repoPlatform!='github' && repoPlatform!='gitlab'}" do: - - log.warn: "PR/MR creation is not supported for repository platform '${repoPlatform}'. Branch '${sourceBranch}' has been pushed; please create a PR/MR manually." + - log.warn: "PR/MR creation is not supported for repository platform '${repoPlatform}'. Branch '${cli.sourceBranch}' has been pushed; please create a PR/MR manually." - var.set: result.status: "no_pr_support" - log.info: "pr-or-mr-create completed: status=${result.status}, platform=${result.platform}" - if: "${result.status=='success'}" - log.info: "PR/MR URL: ${result.prLink}" + log.info: "PR/MR URL: ${result.prLink}" \ No newline at end of file From cb7ef61dcca6eda2977da557e67be1bf2b48c238 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Fri, 19 Jun 2026 20:01:09 +0530 Subject: [PATCH 27/46] fixed compilation issue --- .../actions/zip/commit-and-create-pr.yaml | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml index 45d78825c8b..c95f64efadd 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml @@ -78,15 +78,18 @@ steps: baseBranch: "${#ifBlank(cli.baseBranch, #ifBlank(#git.defaultBranch(cli.sourceDir), 'main'))}" # Step 1: Stage, commit and push - - action.run: - action: git-stage-commit-push - options: - source-dir: "${cli.sourceDir}" - branch-prefix: "${cli.branchPrefix}" - author-name: "${cli.authorName}" - author-email: "${cli.authorEmail}" + - run.fcli: + GIT_STAGE_COMMIT_PUSH: + cmd: fcli action run git-stage-commit-push "--source-dir=${cli.sourceDir}" "--branch-prefix=${cli.branchPrefix}" "--author-name=${cli.authorName}" "--author-email=${cli.authorEmail}" --progress=none + stdout: collect + stderr: collect + on.fail: + - log.warn: + msg: "Failed to run git-stage-commit-push action" + cause: ${GIT_STAGE_COMMIT_PUSH_exception} + - throw: ${GIT_STAGE_COMMIT_PUSH_exception} - var.set: - gitResult: ${result} + gitResult: ${GIT_STAGE_COMMIT_PUSH.result} - if: "${gitResult.status=='no_changes'}" do: @@ -94,16 +97,18 @@ steps: - exit: 0 # Step 2: Create PR/MR from the pushed branch - - action.run: - action: pr-or-mr-create - options: - source-dir: "${cli.sourceDir}" - source-branch: "${gitResult.branchName}" - base-branch: "${baseBranch}" - title: "${cli.title}" - body: "${cli.body}" + - run.fcli: + CREATE_PR: + cmd: fcli action run pr-or-mr-create "--source-dir=${cli.sourceDir}" "--source-branch=${gitResult.branchName}" "--base-branch=${baseBranch}" "--title=${cli.title}" "--body=${cli.body}" --progress=none + stdout: collect + stderr: collect + on.fail: + - log.warn: + msg: "Failed to run pr-or-mr-create action" + cause: ${CREATE_PR_exception} + - throw: ${CREATE_PR_exception} - var.set: - prResult: ${result} + prResult: ${CREATE_PR.result} - log.info: "commit-and-create-pr completed: git=${gitResult.status}, pr=${prResult.status}" - if: "${prResult.status=='success'}" From 402dd1231ec8c42dca1f2883945e41d6f8bd5314 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Fri, 19 Jun 2026 20:07:28 +0530 Subject: [PATCH 28/46] fix spaces for action instructions --- .../actions/zip/commit-and-create-pr.yaml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml index c95f64efadd..795c966e187 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml @@ -83,11 +83,11 @@ steps: cmd: fcli action run git-stage-commit-push "--source-dir=${cli.sourceDir}" "--branch-prefix=${cli.branchPrefix}" "--author-name=${cli.authorName}" "--author-email=${cli.authorEmail}" --progress=none stdout: collect stderr: collect - on.fail: - - log.warn: - msg: "Failed to run git-stage-commit-push action" - cause: ${GIT_STAGE_COMMIT_PUSH_exception} - - throw: ${GIT_STAGE_COMMIT_PUSH_exception} + on.fail: + - log.warn: + msg: "Failed to run git-stage-commit-push action" + cause: ${GIT_STAGE_COMMIT_PUSH_exception} + - throw: ${GIT_STAGE_COMMIT_PUSH_exception} - var.set: gitResult: ${GIT_STAGE_COMMIT_PUSH.result} @@ -102,11 +102,11 @@ steps: cmd: fcli action run pr-or-mr-create "--source-dir=${cli.sourceDir}" "--source-branch=${gitResult.branchName}" "--base-branch=${baseBranch}" "--title=${cli.title}" "--body=${cli.body}" --progress=none stdout: collect stderr: collect - on.fail: - - log.warn: - msg: "Failed to run pr-or-mr-create action" - cause: ${CREATE_PR_exception} - - throw: ${CREATE_PR_exception} + on.fail: + - log.warn: + msg: "Failed to run pr-or-mr-create action" + cause: ${CREATE_PR_exception} + - throw: ${CREATE_PR_exception} - var.set: prResult: ${CREATE_PR.result} From 0556a77dc3f14886e68bb027bfd0dd0f55b4cef8 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Fri, 19 Jun 2026 21:24:39 +0530 Subject: [PATCH 29/46] fixed action loading issues --- .../main/resources/com/fortify/cli/fod/actions/zip/ci.yaml | 3 +-- .../main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml index 483e46b0c19..f595026d4d0 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml @@ -148,8 +148,7 @@ steps: CREATE_PR: cmd: ${#actionCmd('CREATE_PR', '', 'action run commit-and-create-pr')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" skip.if-reason: - - ${#actionCmdSkipFromEnvReason('CREATE_PR', true)} # Skip unless DO_CREATE_PR==true or CREATE_PR_EXTRA_OPTS defined - - ${!remediationsApplied?'Apply remediations was skipped or failed, skipping PR creation':''} + - ${#isBlank(remediationsApplied)?'Apply remediations was skipped or failed, skipping PR creation':''} on.success: - if: ${CREATE_PR.variables.status=='success'} log.info: "Pull/Merge Request successfully created: ${CREATE_PR.variables.prLink}" diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml index 30f23618eaa..2b71f5c7369 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml @@ -208,7 +208,7 @@ steps: - "${#isBlank(aviator.artifactId) ? 'No artifact produced by Aviator audit, nothing to wait for' : ''}" APPLY_REMEDIATIONS: - cmd: ${#fcliCmd('APPLY_REMEDIATIONS', 'aviator ssc apply-remediations')} "--rel=${global.ci.rel}" "--source-dir=${global.ci.sourceDir}" "--progress=none" + cmd: ${#fcliCmd('APPLY_REMEDIATIONS', 'aviator ssc apply-remediations')} "--av=${global.ci.av}" "--source-dir=${global.ci.sourceDir}" "--artifact-id=${aviator.artifactId}" "--progress=none" skip.if-reason: - ${#fcliCmdSkipFromEnvReason('APPLY_REMEDIATIONS', true)} # Skip unless DO_APPLY_REMEDIATIONS==true or APPLY_REMEDIATIONS_ACTION/EXTRA_OPTS defined - ${postScan.skipReason} # Skip if no scans were run @@ -220,8 +220,7 @@ steps: CREATE_PR: cmd: ${#actionCmd('CREATE_PR', '', 'action run commit-and-create-pr')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" skip.if-reason: - - ${#actionCmdSkipFromEnvReason('CREATE_PR', true)} # Skip unless DO_CREATE_PR==true or CREATE_PR_EXTRA_OPTS defined - - ${!remediationsApplied?'Apply remediations was skipped or failed, skipping PR creation':''} + - ${#isBlank(remediationsApplied)?'Apply remediations was skipped or failed, skipping PR creation':''} on.success: - if: ${CREATE_PR.variables.status=='success'} log.info: "Pull/Merge Request successfully created: ${CREATE_PR.variables.prLink}" From 714d80ef263abe2f18967753322b00e57d250579 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Fri, 19 Jun 2026 23:10:05 +0530 Subject: [PATCH 30/46] apply-remediations command requires either --av or --artifact-id --- .../src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml index 2b71f5c7369..0e8d59d4ffc 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml @@ -208,7 +208,7 @@ steps: - "${#isBlank(aviator.artifactId) ? 'No artifact produced by Aviator audit, nothing to wait for' : ''}" APPLY_REMEDIATIONS: - cmd: ${#fcliCmd('APPLY_REMEDIATIONS', 'aviator ssc apply-remediations')} "--av=${global.ci.av}" "--source-dir=${global.ci.sourceDir}" "--artifact-id=${aviator.artifactId}" "--progress=none" + cmd: ${#fcliCmd('APPLY_REMEDIATIONS', 'aviator ssc apply-remediations')} "--av=${global.ci.av}" "--source-dir=${global.ci.sourceDir}" "--progress=none" skip.if-reason: - ${#fcliCmdSkipFromEnvReason('APPLY_REMEDIATIONS', true)} # Skip unless DO_APPLY_REMEDIATIONS==true or APPLY_REMEDIATIONS_ACTION/EXTRA_OPTS defined - ${postScan.skipReason} # Skip if no scans were run From d5cf7f1787141d518bba15bd701d6fd84c2fb972 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Sat, 20 Jun 2026 05:00:40 +0530 Subject: [PATCH 31/46] passing --artifact-id option for apply-remediations command --- .../src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml index 0e8d59d4ffc..a7ba8165004 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml @@ -208,7 +208,7 @@ steps: - "${#isBlank(aviator.artifactId) ? 'No artifact produced by Aviator audit, nothing to wait for' : ''}" APPLY_REMEDIATIONS: - cmd: ${#fcliCmd('APPLY_REMEDIATIONS', 'aviator ssc apply-remediations')} "--av=${global.ci.av}" "--source-dir=${global.ci.sourceDir}" "--progress=none" + cmd: ${#fcliCmd('APPLY_REMEDIATIONS', 'aviator ssc apply-remediations')} "--source-dir=${global.ci.sourceDir}" "--artifact-id=${aviator.artifactId}" "--progress=none" skip.if-reason: - ${#fcliCmdSkipFromEnvReason('APPLY_REMEDIATIONS', true)} # Skip unless DO_APPLY_REMEDIATIONS==true or APPLY_REMEDIATIONS_ACTION/EXTRA_OPTS defined - ${postScan.skipReason} # Skip if no scans were run From e0752e515612ce86d7a1f8994cfaa1ab2d0931df Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Sat, 20 Jun 2026 10:42:07 +0530 Subject: [PATCH 32/46] fixed the action command format --- .../src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml | 2 +- .../src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml index f595026d4d0..1e86941d6b2 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml @@ -146,7 +146,7 @@ steps: - var.set: { remediationsApplied: 'true' } CREATE_PR: - cmd: ${#actionCmd('CREATE_PR', '', 'action run commit-and-create-pr')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" + cmd: ${#actionCmd('CREATE_PR', '', 'commit-and-create-pr')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" skip.if-reason: - ${#isBlank(remediationsApplied)?'Apply remediations was skipped or failed, skipping PR creation':''} on.success: diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml index a7ba8165004..e73d737992a 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml @@ -218,7 +218,7 @@ steps: - var.set: { remediationsApplied: 'true' } CREATE_PR: - cmd: ${#actionCmd('CREATE_PR', '', 'action run commit-and-create-pr')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" + cmd: ${#actionCmd('CREATE_PR', '', 'commit-and-create-pr')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" skip.if-reason: - ${#isBlank(remediationsApplied)?'Apply remediations was skipped or failed, skipping PR creation':''} on.success: From 3e7231235cc43e45ebb6a8f9025a1b27b88be0ec Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Sat, 20 Jun 2026 12:36:19 +0530 Subject: [PATCH 33/46] Fixed generic action run command --- .../cli/generic_action/actions/zip/commit-and-create-pr.yaml | 4 ++-- .../main/resources/com/fortify/cli/fod/actions/zip/ci.yaml | 2 +- .../main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml index 795c966e187..4102a688672 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml @@ -87,7 +87,7 @@ steps: - log.warn: msg: "Failed to run git-stage-commit-push action" cause: ${GIT_STAGE_COMMIT_PUSH_exception} - - throw: ${GIT_STAGE_COMMIT_PUSH_exception} + - throw: "Failed to run git-stage-commit-push action - check logs for details" - var.set: gitResult: ${GIT_STAGE_COMMIT_PUSH.result} @@ -106,7 +106,7 @@ steps: - log.warn: msg: "Failed to run pr-or-mr-create action" cause: ${CREATE_PR_exception} - - throw: ${CREATE_PR_exception} + - throw: "Failed to run pr-or-mr-create action - check logs for details" - var.set: prResult: ${CREATE_PR.result} diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml index 1e86941d6b2..22835736160 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml @@ -146,7 +146,7 @@ steps: - var.set: { remediationsApplied: 'true' } CREATE_PR: - cmd: ${#actionCmd('CREATE_PR', '', 'commit-and-create-pr')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" + cmd: ${#fcliCmd('CREATE_PR', 'action run commit-and-create-pr')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" skip.if-reason: - ${#isBlank(remediationsApplied)?'Apply remediations was skipped or failed, skipping PR creation':''} on.success: diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml index e73d737992a..c2bb6b1f99b 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml @@ -218,7 +218,7 @@ steps: - var.set: { remediationsApplied: 'true' } CREATE_PR: - cmd: ${#actionCmd('CREATE_PR', '', 'commit-and-create-pr')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" + cmd: ${#fcliCmd('CREATE_PR', 'action run commit-and-create-pr')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" skip.if-reason: - ${#isBlank(remediationsApplied)?'Apply remediations was skipped or failed, skipping PR creation':''} on.success: From e9e059c3b1e759fc943236b9d5ce35d177a49a20 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Sat, 20 Jun 2026 13:55:27 +0530 Subject: [PATCH 34/46] Improved the Git SpEL function commit to accept the author name and email --- .../generic_action/actions/zip/git-stage-commit-push.yaml | 8 +------- .../common/action/helper/git/ActionGitSpelFunctions.java | 7 +++---- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml index 5288d80f85a..071366fa8ce 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml @@ -90,12 +90,6 @@ steps: - exit: 0 - log.info: "Changes detected in ${cli.sourceDir}, proceeding." - # Set git author identity - - var.set: - gitAuthorEnv: ${#env.set('GIT_AUTHOR_NAME', cli.authorName).put('GIT_AUTHOR_EMAIL', cli.authorEmail)} - on.fail: - - log.warn: "Warning: Unable to set git author environment variables" - # Create branch - var.set: branchName: ${#git.createBranch(cli.sourceDir, cli.branchPrefix)} @@ -118,7 +112,7 @@ steps: # Commit - var.set: - commitSha: "${#git.commit(cli.sourceDir, cli.commitMessage)}" + commitSha: "${#git.commit(cli.sourceDir, cli.commitMessage, cli.authorName, cli.authorEmail)}" on.fail: - log.warn: msg: "Failed to commit staged changes" diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java index 994f0a03bdf..5dffb721b30 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java @@ -227,7 +227,9 @@ public boolean addAll( returns="The commit SHA of the new commit") public String commit( @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir, - @SpelFunctionParam(name="message", desc="commit message") String message) { + @SpelFunctionParam(name="message", desc="commit message") String message, + @SpelFunctionParam(name="name", desc="commit author name") String name, + @SpelFunctionParam(name="email", desc="commit author email") String email) { try (var git = openGit(sourceDir)) { if (git == null) { throw new FcliSimpleException("Not a git repository: " + sourceDir); @@ -237,9 +239,6 @@ public String commit( throw new FcliSimpleException("No changes to commit"); } - String name = "fcli-actions[bot]"; //TODO - Check if we can get author info from env or git config - String email = "fcli-actions@opentext.com"; //TODO - Check if we can get author info from env or git config - var commitResult = git.commit() .setMessage(message) .setAuthor(name, email) From 754d04561d605fb2afbeee0fcc15071f24846421 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Sat, 20 Jun 2026 17:24:49 +0530 Subject: [PATCH 35/46] Fixed push related issues, added more logs to understand it better --- .../actions/zip/git-stage-commit-push.yaml | 3 +- .../actions/zip/pr-or-mr-create.yaml | 3 +- .../helper/git/ActionGitSpelFunctions.java | 78 ++++++++++++++----- 3 files changed, 62 insertions(+), 22 deletions(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml index 071366fa8ce..a2038d492ba 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml @@ -82,7 +82,6 @@ steps: do: - log.info: "No changes detected in ${cli.sourceDir}, skipping commit and push." - var.set: - result: ${T(java.util.LinkedHashMap).new()} result.status: "no_changes" result.branchName: "" result.commitSha: "" @@ -119,6 +118,7 @@ steps: cause: ${lastException} - throw: ${lastException} - log.info: "Committed changes: ${commitSha}" + - log.debug: "Remote URL: ${gitRepoInfo.repository.remoteUrl}" # Push - var.set: @@ -138,7 +138,6 @@ steps: - log.info: "Pushed branch to remote: ${pushedRef}" - var.set: - result: ${T(java.util.LinkedHashMap).new()} result.status: "success" result.branchName: "${branchName}" result.commitSha: "${commitSha}" diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml index 18e560c185c..2f3d8caba08 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml @@ -67,7 +67,8 @@ steps: # Compute base branch: CLI option/env var -> git remote HEAD -> "main" - var.set: baseBranch: "${#ifBlank(cli.baseBranch, #ifBlank(#git.defaultBranch(cli.sourceDir), 'main'))}" - + - log.debug: "Computed base branch for PR/MR: ${baseBranch}" + - if: "${#isBlank(cli.sourceBranch)}" throw: "Option '--source-branch' is required. Pass the branch created by git-stage-commit-push." diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java index 5dffb721b30..8d6b768d1c1 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java @@ -23,9 +23,7 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; -import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.RefSpec; -import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import com.fasterxml.jackson.databind.node.ObjectNode; import com.formkiq.graalvm.annotations.Reflectable; @@ -182,17 +180,17 @@ public boolean hasChanges( } } - @SpelFunction(cat=util, desc="Creates a new branch in the local git repository and checks it out. The branch name is based on the provided prefix and a timestamp suffix to ensure uniqueness (e.g., 'fcli/remediation/20260520-103045').", + @SpelFunction(cat=util, desc="Creates a new branch in the local git repository and checks it out. The branch name is based on the provided prefix and a timestamp suffix to ensure uniqueness.", returns="The name of the created branch") public String createBranch( @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir, - @SpelFunctionParam(name="branchPrefix", desc="prefix for the branch name (e.g., 'fcli/remediation')") String branchPrefix) { + @SpelFunctionParam(name="branchPrefix", desc="prefix for the branch name (e.g., 'fcli/aviator-remediations')") String branchPrefix) { try (var git = openGit(sourceDir)) { if (git == null) { throw new FcliSimpleException("Not a git repository: " + sourceDir); } var timestamp = java.time.LocalDateTime.now() - .format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")); + .format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss-SSS")); var branchName = branchPrefix + "/" + timestamp; git.checkout() .setCreateBranch(true) @@ -282,50 +280,91 @@ public String push( repo.getConfig().setString("remote", remote, "url", remoteUrl); repo.getConfig().save(); } - CredentialsProvider credentialsProvider = detectCredentialsProvider(); - var token = System.getenv("GITHUB_TOKEN"); - - if (token != null) { - credentialsProvider = new UsernamePasswordCredentialsProvider("x-access-token", token); - } - + var credentialsProvider = CredentialsProviderFactory.detectAndGetJGitProvider(); if (credentialsProvider == null) { log.debug("PUSH DEBUG: No credentials provider detected - push will likely fail"); } else { log.debug("PUSH DEBUG: Using credentials provider={}", credentialsProvider.getClass().getName()); } + String fullBranchRef = "refs/heads/" + branchName; + log.debug("PUSH DETAILS: branch={}, remote={}, remoteUrl={}, fullBranchRef={}", + branchName, + remote, + remoteUrl, + fullBranchRef + ); var refSpec = new RefSpec(fullBranchRef + ":" + fullBranchRef); + if (credentialsProvider != null) { + log.debug("CREDENTIALS: type={}, class={}", + credentialsProvider.getClass().getSimpleName(), + credentialsProvider.getClass().getName() + ); + } + log.debug("PUSH COMMAND SETUP: remote={}, refSpec={}, timeout=300s, credentialsSet={}", + remote, + refSpec.toString(), + credentialsProvider != null + ); var fetchCmd = git.fetch().setRemote(remote); if (credentialsProvider != null) { fetchCmd.setCredentialsProvider(credentialsProvider); } - fetchCmd.call(); + + try{ + var fetchResult = fetchCmd.call(); + log.debug("Fetch completed with {} ref updates", + fetchResult != null ? fetchResult.getAdvertisedRefs().size() : 0 + ); + } catch(Exception e){ + log.warn("Fetch failed (but continuing with push): {}", e.getMessage(), e); + } + var pushCmd = git.push() .setRemote(remote) .setRefSpecs(refSpec) - .setTimeout(60); + .setTimeout(300); if (credentialsProvider != null) { pushCmd.setCredentialsProvider(credentialsProvider); } var results = pushCmd.call(); + log.debug("Push command completed. credentialsProvider: {}", + credentialsProvider != null ? credentialsProvider.getClass().getSimpleName() : "null" + ); + + // Don't convert to ArrayList - just iterate directly + // We can't use .isEmpty() or .size() on Iterable, so remove those checks + StoredConfig config = repo.getConfig(); config.setString("branch", branchName, "remote", remote); config.setString("branch", branchName, "merge", fullBranchRef); config.save(); boolean success = false; + boolean hasResults = false; for (var result : results) { + hasResults = true; var messages = result.getMessages(); + if (messages != null && !messages.isBlank()) { + log.debug("Push result messages: {}", messages); + } + for (var update : result.getRemoteUpdates()) { var status = update.getStatus(); + log.debug("Push update: status={}, remoteName={}, message='{}', forceUpdate={}", + status, + update.getRemoteName(), + update.getMessage() != null ? update.getMessage() : "null", + update.isForceUpdate() + ); switch (status) { case OK: case UP_TO_DATE: success = true; + log.debug("Push successful: {}", update.getRemoteName()); break; case REJECTED_NONFASTFORWARD: @@ -339,12 +378,17 @@ public String push( "Push rejected: " + "status=" + status + ", remote=" + update.getRemoteName() - + ", message=" + update.getMessage() + + ", message=" + (update.getMessage() != null ? update.getMessage() : "no message") ); } } } + if (!hasResults) { + log.warn("Push returned empty results - push may have failed silently"); + throw new FcliSimpleException("Push completed but returned no results"); + } + if (!success) { throw new FcliSimpleException("Push completed but no refs were updated (likely auth or permission issue)"); } @@ -448,10 +492,6 @@ private Git openGit(String sourceDir) { } } - private CredentialsProvider detectCredentialsProvider() { - return CredentialsProviderFactory.detectAndGetJGitProvider(); - } - private static String selectRemote(Repository repo) { try { var remotes = repo.getRemoteNames(); From fef4ff83d1b40c6472eddfd92de1143caba57285 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Mon, 22 Jun 2026 14:06:59 +0530 Subject: [PATCH 36/46] Improved the PR creation steps in fcli actions --- .../actions/zip/commit-and-create-pr.yaml | 14 +++++++++---- .../actions/zip/pr-or-mr-create.yaml | 21 ++++++++++++++++--- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml index 4102a688672..70be3cf8aa6 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml @@ -87,14 +87,20 @@ steps: - log.warn: msg: "Failed to run git-stage-commit-push action" cause: ${GIT_STAGE_COMMIT_PUSH_exception} - - throw: "Failed to run git-stage-commit-push action - check logs for details" - var.set: - gitResult: ${GIT_STAGE_COMMIT_PUSH.result} + gitResult: "${GIT_STAGE_COMMIT_PUSH.result?:{status: 'error', branchName: '', commitSha: '', pushedRef: ''}}" + - if: "${gitResult.status=='error'}" + do: + - log.warn: "git-stage-commit-push failed with status 'error'. Check logs for details." + - throw: "git-stage-commit-push failed with status 'error'. Check logs for details." + - log.debug: "git-stage-commit-push completed: status=${gitResult.status}" + - if: "${gitResult.status=='no_changes'}" do: - log.info: "No changes detected, skipping PR/MR creation." - exit: 0 + - log.debug: "changes are pushed to the remote branch '${gitResult.branchName}' and stepping to PR create step" # Step 2: Create PR/MR from the pushed branch - run.fcli: @@ -108,8 +114,8 @@ steps: cause: ${CREATE_PR_exception} - throw: "Failed to run pr-or-mr-create action - check logs for details" - var.set: - prResult: ${CREATE_PR.result} + prResult: "${CREATE_PR.result?:{status: 'error', prLink: ''}}" - - log.info: "commit-and-create-pr completed: git=${gitResult.status}, pr=${prResult.status}" + - log.debug: "commit-and-create-pr completed: git=${gitResult.status}, pr=${prResult.status}" - if: "${prResult.status=='success'}" log.info: "PR/MR URL: ${prResult.prLink}" \ No newline at end of file diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml index 2f3d8caba08..7d5324997c7 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml @@ -72,6 +72,8 @@ steps: - if: "${#isBlank(cli.sourceBranch)}" throw: "Option '--source-branch' is required. Pass the branch created by git-stage-commit-push." + - log.debug: "Started the action create PR/MR: for base branch '${baseBranch}' and source branch '${cli.sourceBranch}'" + - log.debug: "PR/MR configuration: platform detection from '${cli.sourceDir}', source=${cli.sourceBranch}, base=${baseBranch}" # Detect repository platform from git remote URL @@ -80,7 +82,6 @@ steps: - log.debug: "Detected repository platform: ${repoPlatform}" - var.set: - result: ${T(java.util.LinkedHashMap).new()} result.status: "error" result.prLink: "" result.platform: "${repoPlatform}" @@ -102,13 +103,20 @@ steps: - var.set: result.status: "error" result.errorMessage: "${lastException.message}" - - if: ${pr!=null} + - if: ${pr!=null && pr.html_url!=null && pr.number!=null} do: - var.set: result.status: "success" result.prLink: "${pr.html_url}" - log.info: "Created GitHub Pull Request #${pr.number}: ${pr.html_url}" + - if: ${pr==null || pr.html_url==null} + do: + - log.warn: "PR creation returned empty or invalid response: ${pr}" + - var.set: + result.status: "error" + result.errorMessage: "GitHub PR creation did not return expected response. Check logs for details." + # GitLab MR creation - if: "${repoPlatform=='gitlab'}" do: @@ -123,12 +131,19 @@ steps: - var.set: result.status: "error" result.errorMessage: "${lastException.message}" - - if: ${mr!=null} + - if: ${mr!=null && mr.web_url!=null && mr.iid!=null} do: - var.set: result.status: "success" result.prLink: "${mr.web_url}" - log.info: "Created GitLab Merge Request !${mr.iid}: ${mr.web_url}" + + - if: ${mr==null || mr.web_url==null} + do: + - log.warn: "Merge Request creation returned empty or invalid response: ${mr}" + - var.set: + result.status: "error" + result.errorMessage: "GitLab MR creation did not return expected response. Check logs for details." # Unsupported repository platform - if: "${repoPlatform!='github' && repoPlatform!='gitlab'}" From e57f69c61f7eca93b8a939a1a2e5f7e64ffac018 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Tue, 23 Jun 2026 10:56:10 +0530 Subject: [PATCH 37/46] Indentation fix and results status update for both git activities and PR/MR creation --- .../actions/zip/commit-and-create-pr.yaml | 12 ++++++------ .../actions/zip/git-stage-commit-push.yaml | 2 +- .../generic_action/actions/zip/pr-or-mr-create.yaml | 11 ++++++----- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml index 70be3cf8aa6..3aab0df029b 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml @@ -94,12 +94,12 @@ steps: do: - log.warn: "git-stage-commit-push failed with status 'error'. Check logs for details." - throw: "git-stage-commit-push failed with status 'error'. Check logs for details." - - log.debug: "git-stage-commit-push completed: status=${gitResult.status}" - if: "${gitResult.status=='no_changes'}" do: - log.info: "No changes detected, skipping PR/MR creation." - exit: 0 + - log.debug: "changes are pushed to the remote branch '${gitResult.branchName}' and stepping to PR create step" # Step 2: Create PR/MR from the pushed branch @@ -108,14 +108,14 @@ steps: cmd: fcli action run pr-or-mr-create "--source-dir=${cli.sourceDir}" "--source-branch=${gitResult.branchName}" "--base-branch=${baseBranch}" "--title=${cli.title}" "--body=${cli.body}" --progress=none stdout: collect stderr: collect - on.fail: - - log.warn: - msg: "Failed to run pr-or-mr-create action" - cause: ${CREATE_PR_exception} - - throw: "Failed to run pr-or-mr-create action - check logs for details" + on.fail: + - log.warn: + msg: "Failed to run pr-or-mr-create action" + cause: ${CREATE_PR_exception} - var.set: prResult: "${CREATE_PR.result?:{status: 'error', prLink: ''}}" - log.debug: "commit-and-create-pr completed: git=${gitResult.status}, pr=${prResult.status}" + - if: "${prResult.status=='success'}" log.info: "PR/MR URL: ${prResult.prLink}" \ No newline at end of file diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml index a2038d492ba..0b36fb42ece 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml @@ -69,7 +69,7 @@ steps: # Validate git repository - var.set: gitRepoInfo: ${#git.localRepo(cli.sourceDir)} - - if: ${gitRepoInfo==null} + - if: ${#isBlank(gitRepoInfo)} throw: "Source directory '${cli.sourceDir}' is not a git repository." - if: "${#isBlank(gitRepoInfo.repository.remoteUrl)}" throw: "Git repository has no remote URL configured. A remote is required for push." diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml index 7d5324997c7..7ae587eb562 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml @@ -41,7 +41,7 @@ cli.options: names: --source-branch description: >- Source branch to raise the PR/MR from. - required: false + required: true default: "${#env('SOURCE_BRANCH')?:''}" baseBranch: names: --base-branch @@ -100,17 +100,18 @@ steps: msg: "Failed to create GitHub Pull Request from '${cli.sourceBranch}' to '${baseBranch}'" cause: ${lastException} - log.warn: "Troubleshooting: Verify branch '${cli.sourceBranch}' exists on remote and base branch '${baseBranch}' is valid" + - log.debug: "Failed to create GitHub PR: ${lastException.message}" - var.set: result.status: "error" result.errorMessage: "${lastException.message}" - - if: ${pr!=null && pr.html_url!=null && pr.number!=null} + - if: ${#isNotBlank(pr)} && ${#isNotBlank(pr.html_url)} && ${#isNotBlank(pr.number)} do: - var.set: result.status: "success" result.prLink: "${pr.html_url}" - log.info: "Created GitHub Pull Request #${pr.number}: ${pr.html_url}" - - if: ${pr==null || pr.html_url==null} + - if: ${#isBlank(pr)} || ${#isBlank(pr.html_url)} do: - log.warn: "PR creation returned empty or invalid response: ${pr}" - var.set: @@ -131,14 +132,14 @@ steps: - var.set: result.status: "error" result.errorMessage: "${lastException.message}" - - if: ${mr!=null && mr.web_url!=null && mr.iid!=null} + - if: ${#isNotBlank(mr)} && ${#isNotBlank(mr.web_url)} && ${#isNotBlank(mr.iid)} do: - var.set: result.status: "success" result.prLink: "${mr.web_url}" - log.info: "Created GitLab Merge Request !${mr.iid}: ${mr.web_url}" - - if: ${mr==null || mr.web_url==null} + - if: ${#isBlank(mr)} || ${#isBlank(mr.web_url)} do: - log.warn: "Merge Request creation returned empty or invalid response: ${mr}" - var.set: From 9184e814fc793d2949ceee6a39bc594448c2b3ed Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Tue, 23 Jun 2026 11:06:28 +0530 Subject: [PATCH 38/46] Fixing indentation and build issues --- .../generic_action/actions/zip/commit-and-create-pr.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml index 3aab0df029b..22fccec0ae1 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml @@ -108,10 +108,10 @@ steps: cmd: fcli action run pr-or-mr-create "--source-dir=${cli.sourceDir}" "--source-branch=${gitResult.branchName}" "--base-branch=${baseBranch}" "--title=${cli.title}" "--body=${cli.body}" --progress=none stdout: collect stderr: collect - on.fail: - - log.warn: - msg: "Failed to run pr-or-mr-create action" - cause: ${CREATE_PR_exception} + on.fail: + - log.warn: + msg: "Failed to run pr-or-mr-create action" + cause: ${CREATE_PR_exception} - var.set: prResult: "${CREATE_PR.result?:{status: 'error', prLink: ''}}" From 8ead46da970d555cb1e4290b5356cac111e160df Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Wed, 24 Jun 2026 07:52:08 +0530 Subject: [PATCH 39/46] Acceessing the result of one fcli action in another and improved logging to print the read results of called fcli action --- .../generic_action/actions/zip/commit-and-create-pr.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml index 22fccec0ae1..4815d458243 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml @@ -80,15 +80,16 @@ steps: # Step 1: Stage, commit and push - run.fcli: GIT_STAGE_COMMIT_PUSH: - cmd: fcli action run git-stage-commit-push "--source-dir=${cli.sourceDir}" "--branch-prefix=${cli.branchPrefix}" "--author-name=${cli.authorName}" "--author-email=${cli.authorEmail}" --progress=none + cmd: fcli action run git-stage-commit-push "--source-dir=${cli.sourceDir}" "--branch-prefix=${cli.branchPrefix}" "--author-name=${cli.authorName}" "--author-email=${cli.authorEmail}" --output=json --progress=none stdout: collect stderr: collect on.fail: - log.warn: msg: "Failed to run git-stage-commit-push action" cause: ${GIT_STAGE_COMMIT_PUSH_exception} + - log.debug: "GIT_STAGE_COMMIT_PUSH variable content: ${GIT_STAGE_COMMIT_PUSH}" - var.set: - gitResult: "${GIT_STAGE_COMMIT_PUSH.result?:{status: 'error', branchName: '', commitSha: '', pushedRef: ''}}" + gitResult: "${#jsonParse(GIT_STAGE_COMMIT_PUSH.stdout)}" - if: "${gitResult.status=='error'}" do: @@ -113,7 +114,7 @@ steps: msg: "Failed to run pr-or-mr-create action" cause: ${CREATE_PR_exception} - var.set: - prResult: "${CREATE_PR.result?:{status: 'error', prLink: ''}}" + prResult: "${#jsonParse(CREATE_PR.stdout)}" - log.debug: "commit-and-create-pr completed: git=${gitResult.status}, pr=${prResult.status}" From 25d8e3640e3cc0b6c31e63df7bf477802d8a1898 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Wed, 24 Jun 2026 09:53:33 +0530 Subject: [PATCH 40/46] Storing the results in a variable and then accessing it using SpEL function #var --- .../actions/zip/commit-and-create-pr.yaml | 14 +++++++++----- .../actions/zip/git-stage-commit-push.yaml | 5 ++++- .../actions/zip/pr-or-mr-create.yaml | 5 ++++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml index 4815d458243..52366a1f56d 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml @@ -80,16 +80,17 @@ steps: # Step 1: Stage, commit and push - run.fcli: GIT_STAGE_COMMIT_PUSH: - cmd: fcli action run git-stage-commit-push "--source-dir=${cli.sourceDir}" "--branch-prefix=${cli.branchPrefix}" "--author-name=${cli.authorName}" "--author-email=${cli.authorEmail}" --output=json --progress=none + cmd: fcli action run git-stage-commit-push "--source-dir=${cli.sourceDir}" "--branch-prefix=${cli.branchPrefix}" "--author-name=${cli.authorName}" "--author-email=${cli.authorEmail}" --progress=none --store=git_stage_result stdout: collect stderr: collect on.fail: - log.warn: msg: "Failed to run git-stage-commit-push action" cause: ${GIT_STAGE_COMMIT_PUSH_exception} - - log.debug: "GIT_STAGE_COMMIT_PUSH variable content: ${GIT_STAGE_COMMIT_PUSH}" + - throw: "git-stage-commit-push failed" + - var.set: - gitResult: "${#jsonParse(GIT_STAGE_COMMIT_PUSH.stdout)}" + gitResult: "${#var('git_stage_result')}" - if: "${gitResult.status=='error'}" do: @@ -106,15 +107,18 @@ steps: # Step 2: Create PR/MR from the pushed branch - run.fcli: CREATE_PR: - cmd: fcli action run pr-or-mr-create "--source-dir=${cli.sourceDir}" "--source-branch=${gitResult.branchName}" "--base-branch=${baseBranch}" "--title=${cli.title}" "--body=${cli.body}" --progress=none + cmd: fcli action run pr-or-mr-create "--source-dir=${cli.sourceDir}" "--source-branch=${gitResult.branchName}" "--base-branch=${baseBranch}" "--title=${cli.title}" "--body=${cli.body}" --progress=none --store=pr_create_result stdout: collect stderr: collect on.fail: - log.warn: msg: "Failed to run pr-or-mr-create action" cause: ${CREATE_PR_exception} + - throw: "pr-or-mr-create failed" + + # Retrieve PR result - var.set: - prResult: "${#jsonParse(CREATE_PR.stdout)}" + prResult: "${#var('pr_create_result')}" - log.debug: "commit-and-create-pr completed: git=${gitResult.status}, pr=${prResult.status}" diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml index 0b36fb42ece..e4821a203c0 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml @@ -142,4 +142,7 @@ steps: result.branchName: "${branchName}" result.commitSha: "${commitSha}" result.pushedRef: "${pushedRef}" - - log.info: "git-stage-commit-push completed: branch=${result.branchName}, commit=${result.commitSha}" \ No newline at end of file + - log.info: "git-stage-commit-push completed: branch=${result.branchName}, commit=${result.commitSha}" + + - out.write: + object: "${result}" \ No newline at end of file diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml index 7ae587eb562..8ecb8a5f322 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml @@ -155,4 +155,7 @@ steps: - log.info: "pr-or-mr-create completed: status=${result.status}, platform=${result.platform}" - if: "${result.status=='success'}" - log.info: "PR/MR URL: ${result.prLink}" \ No newline at end of file + log.info: "PR/MR URL: ${result.prLink}" + + - out.write: + object: "${result}" \ No newline at end of file From abc23d611d1739fa2f37a69331127f7b51671a1e Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Wed, 24 Jun 2026 22:33:48 +0530 Subject: [PATCH 41/46] Removed the wrapper actions, CI now directly references the git-push-changes and create-pr actions. --- .../actions/zip/commit-and-create-pr.yaml | 126 ------------------ .../{pr-or-mr-create.yaml => create-pr.yaml} | 17 ++- ...commit-push.yaml => git-push-changes.yaml} | 15 ++- .../com/fortify/cli/fod/actions/zip/ci.yaml | 14 +- .../com/fortify/cli/ssc/actions/zip/ci.yaml | 14 +- 5 files changed, 48 insertions(+), 138 deletions(-) delete mode 100644 fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml rename fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/{pr-or-mr-create.yaml => create-pr.yaml} (92%) rename fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/{git-stage-commit-push.yaml => git-push-changes.yaml} (91%) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml deleted file mode 100644 index 52366a1f56d..00000000000 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/commit-and-create-pr.yaml +++ /dev/null @@ -1,126 +0,0 @@ -# yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json -author: Fortify -usage: - header: Stage, commit, push and create a pull/merge request - description: | - Convenience wrapper that combines `git-stage-commit-push` and `pr-or-mr-create` - into a single action. Detects local changes, commits them to a new branch, pushes - to the remote, then raises a pull request (GitHub) or merge request (GitLab). - - For more control, run `git-stage-commit-push` and `pr-or-mr-create` separately. - - This action requires: - - Git repository access (local clone with a configured remote) - - A CI token: GITHUB_TOKEN/GH_TOKEN (GitHub), CI_JOB_TOKEN (GitLab), - SYSTEM_ACCESSTOKEN (Azure DevOps), or BITBUCKET_TOKEN (Bitbucket) - - Configuration via CLI options or environment variables: - - `--source-dir` / `SOURCE_DIR` -- Directory where changes are detected (default: '.') - - `--branch-prefix` / `BRANCH_PREFIX` -- Branch name prefix (default: "fcli/aviator-remediations") - - `--title` / `PR_TITLE` -- PR/MR title - - `--body` / `PR_BODY` -- PR/MR description - - `--base-branch` / `BASE_BRANCH` -- Target branch for the PR/MR (default: auto-detected from remote, fallback "main") - - `--author-name` / `GIT_AUTHOR_NAME` -- Commit author name (default: "fcli-aviator[bot]") - - `--author-email` / `GIT_AUTHOR_EMAIL` -- Commit author email - -config: - output: immediate - -cli.options: - sourceDir: - names: --source-dir, -s - description: >- - Directory where changes are detected. - Defaults to current working directory if not specified. - required: false - default: "${#env('SOURCE_DIR')?:'.'}" - branchPrefix: - names: --branch-prefix - description: >- - Prefix for the new branch name. - required: false - default: "${#env('BRANCH_PREFIX')?:'fcli/aviator-remediations'}" - title: - names: --title, -t - description: >- - PR/MR title. - required: false - default: "${#env('PR_TITLE')?:'fix: Fortify auto-remediation fixes [Generated by fcli aviator]'}" - body: - names: --body - description: >- - PR/MR description. - required: false - default: "${#env('PR_BODY')?:'This pull request contains changes applied by fcli aviator.'}" - baseBranch: - names: --base-branch - description: >- - Target branch for the PR/MR. Auto-detected from the remote HEAD if not specified, - falling back to "main". - required: false - default: "${#env('BASE_BRANCH')?:''}" - authorName: - names: --author-name - description: >- - Git author name for the commit. - required: false - default: "${#env('GIT_AUTHOR_NAME')?:'fcli-aviator[bot]'}" - authorEmail: - names: --author-email - description: >- - Git author email for the commit. - required: false - default: "${#env('GIT_AUTHOR_EMAIL')?:'fcli-aviator@opentext.com'}" - -steps: - # Compute base branch: CLI option/env var -> git remote HEAD -> "main" - - var.set: - baseBranch: "${#ifBlank(cli.baseBranch, #ifBlank(#git.defaultBranch(cli.sourceDir), 'main'))}" - - # Step 1: Stage, commit and push - - run.fcli: - GIT_STAGE_COMMIT_PUSH: - cmd: fcli action run git-stage-commit-push "--source-dir=${cli.sourceDir}" "--branch-prefix=${cli.branchPrefix}" "--author-name=${cli.authorName}" "--author-email=${cli.authorEmail}" --progress=none --store=git_stage_result - stdout: collect - stderr: collect - on.fail: - - log.warn: - msg: "Failed to run git-stage-commit-push action" - cause: ${GIT_STAGE_COMMIT_PUSH_exception} - - throw: "git-stage-commit-push failed" - - - var.set: - gitResult: "${#var('git_stage_result')}" - - - if: "${gitResult.status=='error'}" - do: - - log.warn: "git-stage-commit-push failed with status 'error'. Check logs for details." - - throw: "git-stage-commit-push failed with status 'error'. Check logs for details." - - - if: "${gitResult.status=='no_changes'}" - do: - - log.info: "No changes detected, skipping PR/MR creation." - - exit: 0 - - - log.debug: "changes are pushed to the remote branch '${gitResult.branchName}' and stepping to PR create step" - - # Step 2: Create PR/MR from the pushed branch - - run.fcli: - CREATE_PR: - cmd: fcli action run pr-or-mr-create "--source-dir=${cli.sourceDir}" "--source-branch=${gitResult.branchName}" "--base-branch=${baseBranch}" "--title=${cli.title}" "--body=${cli.body}" --progress=none --store=pr_create_result - stdout: collect - stderr: collect - on.fail: - - log.warn: - msg: "Failed to run pr-or-mr-create action" - cause: ${CREATE_PR_exception} - - throw: "pr-or-mr-create failed" - - # Retrieve PR result - - var.set: - prResult: "${#var('pr_create_result')}" - - - log.debug: "commit-and-create-pr completed: git=${gitResult.status}, pr=${prResult.status}" - - - if: "${prResult.status=='success'}" - log.info: "PR/MR URL: ${prResult.prLink}" \ No newline at end of file diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml similarity index 92% rename from fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml rename to fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml index 8ecb8a5f322..76762d5fa20 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/pr-or-mr-create.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml @@ -64,13 +64,13 @@ cli.options: default: "${#env('PR_BODY')?:'This pull request contains changes applied by fcli aviator.'}" steps: + - if: "${#isBlank(cli.sourceBranch)}" + throw: "Option '--source-branch' is required. Pass the branch created by git-stage-commit-push." + # Compute base branch: CLI option/env var -> git remote HEAD -> "main" - var.set: baseBranch: "${#ifBlank(cli.baseBranch, #ifBlank(#git.defaultBranch(cli.sourceDir), 'main'))}" - - log.debug: "Computed base branch for PR/MR: ${baseBranch}" - - - if: "${#isBlank(cli.sourceBranch)}" - throw: "Option '--source-branch' is required. Pass the branch created by git-stage-commit-push." + - log.debug: "Computed base branch for PR/MR: ${baseBranch}" - log.debug: "Started the action create PR/MR: for base branch '${baseBranch}' and source branch '${cli.sourceBranch}'" @@ -104,6 +104,9 @@ steps: - var.set: result.status: "error" result.errorMessage: "${lastException.message}" + - throw: + msg: "Failed to create GitHub Pull Request from '${cli.sourceBranch}' to '${baseBranch}'" + cause: ${lastException} - if: ${#isNotBlank(pr)} && ${#isNotBlank(pr.html_url)} && ${#isNotBlank(pr.number)} do: - var.set: @@ -117,7 +120,7 @@ steps: - var.set: result.status: "error" result.errorMessage: "GitHub PR creation did not return expected response. Check logs for details." - + - throw: "GitHub PR creation did not return expected response. Check logs for details." # GitLab MR creation - if: "${repoPlatform=='gitlab'}" do: @@ -132,6 +135,9 @@ steps: - var.set: result.status: "error" result.errorMessage: "${lastException.message}" + - throw: + msg: "Failed to create GitLab Merge Request from '${cli.sourceBranch}' to '${baseBranch}'" + cause: ${lastException} - if: ${#isNotBlank(mr)} && ${#isNotBlank(mr.web_url)} && ${#isNotBlank(mr.iid)} do: - var.set: @@ -145,6 +151,7 @@ steps: - var.set: result.status: "error" result.errorMessage: "GitLab MR creation did not return expected response. Check logs for details." + - throw: "GitLab MR creation did not return expected response. Check logs for details." # Unsupported repository platform - if: "${repoPlatform!='github' && repoPlatform!='gitlab'}" diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-push-changes.yaml similarity index 91% rename from fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml rename to fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-push-changes.yaml index e4821a203c0..98cfeaf8c7e 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-stage-commit-push.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-push-changes.yaml @@ -86,6 +86,8 @@ steps: result.branchName: "" result.commitSha: "" result.pushedRef: "" + - out.write: + object: "${result}" - exit: 0 - log.info: "Changes detected in ${cli.sourceDir}, proceeding." @@ -93,8 +95,11 @@ steps: - var.set: branchName: ${#git.createBranch(cli.sourceDir, cli.branchPrefix)} on.fail: + - var.set: + result.status: "error" + result.errorMessage: "${lastException.message}" - log.warn: - msg: "Failed to create branch with prefix '${cli.branchPrefix}'" + msg: "Failed to create branch with prefix `'${cli.branchPrefix}`'" cause: ${lastException} - throw: ${lastException} - log.info: "Created branch: ${branchName}" @@ -103,6 +108,9 @@ steps: - var.set: staged: ${#git.addAll(cli.sourceDir)} on.fail: + - var.set: + result.status: "error" + result.errorMessage: "${lastException.message}" - log.warn: msg: "Failed to stage changes in ${cli.sourceDir}" cause: ${lastException} @@ -113,6 +121,9 @@ steps: - var.set: commitSha: "${#git.commit(cli.sourceDir, cli.commitMessage, cli.authorName, cli.authorEmail)}" on.fail: + - var.set: + result.status: "error" + result.errorMessage: "${lastException.message}" - log.warn: msg: "Failed to commit staged changes" cause: ${lastException} @@ -125,6 +136,8 @@ steps: pushedRef: ${#git.push(cli.sourceDir, branchName)} on.fail: - var.set: + result.status: "error" + result.errorMessage: "${lastException.message}" remoteUrl: "${gitRepoInfo.repository.remoteUrl?:'unknown'}" exceptionMsg: "${lastException.message}" - log.warn: diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml index 22835736160..02c50cf8af2 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml @@ -145,17 +145,25 @@ steps: on.success: - var.set: { remediationsApplied: 'true' } + GIT_PUSH_CHANGES: + cmd: ${#fcliCmd('GIT_PUSH_CHANGES', 'action run git-push-changes')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" + skip.if-reason: + - ${#isBlank(remediationsApplied)?'Apply remediations was skipped or failed, skipping Git operations':''} + on.fail: + - log.warn: "Git stage/commit/push failed" + CREATE_PR: - cmd: ${#fcliCmd('CREATE_PR', 'action run commit-and-create-pr')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" + cmd: ${#fcliCmd('CREATE_PR', 'action run create-pr')} "--source-dir=${global.ci.sourceDir}" "--source-branch=${GIT_PUSH_CHANGES.variables.branchName}" "--progress=none" skip.if-reason: - - ${#isBlank(remediationsApplied)?'Apply remediations was skipped or failed, skipping PR creation':''} + - ${GIT_PUSH_CHANGES.dependencySkipReason} + - "${GIT_PUSH_CHANGES.variables.status=='no_changes' ? 'No changes detected, skipping PR/MR creation' : ''}" on.success: - if: ${CREATE_PR.variables.status=='success'} log.info: "Pull/Merge Request successfully created: ${CREATE_PR.variables.prLink}" - if: ${CREATE_PR.variables.status=='no_changes'} log.info: "No changes detected - PR/MR creation skipped" - if: ${CREATE_PR.variables.status=='no_pr_support'} - log.warn: "Branch created and pushed, but PR/MR creation not supported for ${CREATE_PR.variables.ciSystem}" + log.warn: "Branch created and pushed, but PR/MR creation not supported for platform '${CREATE_PR.variables.platform}'" - if: ${CREATE_PR.variables.status=='error'} log.warn: "PR/MR creation failed: ${CREATE_PR.variables.errorMessage}" diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml index c2bb6b1f99b..e0d8c9fd855 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml @@ -217,17 +217,25 @@ steps: on.success: - var.set: { remediationsApplied: 'true' } + GIT_PUSH_CHANGES: + cmd: ${#fcliCmd('GIT_PUSH_CHANGES', 'action run git-push-changes')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" + skip.if-reason: + - ${#isBlank(remediationsApplied)?'Apply remediations was skipped or failed, skipping Git operations':''} + on.fail: + - log.warn: "Git stage/commit/push failed" + CREATE_PR: - cmd: ${#fcliCmd('CREATE_PR', 'action run commit-and-create-pr')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" + cmd: ${#fcliCmd('CREATE_PR', 'action run create-pr')} "--source-dir=${global.ci.sourceDir}" "--source-branch=${GIT_PUSH_CHANGES.variables.branchName}" "--progress=none" skip.if-reason: - - ${#isBlank(remediationsApplied)?'Apply remediations was skipped or failed, skipping PR creation':''} + - ${GIT_PUSH_CHANGES.dependencySkipReason} + - "${GIT_PUSH_CHANGES.variables.status=='no_changes' ? 'No changes detected, skipping PR/MR creation' : ''}" on.success: - if: ${CREATE_PR.variables.status=='success'} log.info: "Pull/Merge Request successfully created: ${CREATE_PR.variables.prLink}" - if: ${CREATE_PR.variables.status=='no_changes'} log.info: "No changes detected - PR/MR creation skipped" - if: ${CREATE_PR.variables.status=='no_pr_support'} - log.warn: "Branch created and pushed, but PR/MR creation not supported for ${CREATE_PR.variables.ciSystem}" + log.warn: "Branch created and pushed, but PR/MR creation not supported for platform '${CREATE_PR.variables.platform}'" - if: ${CREATE_PR.variables.status=='error'} log.warn: "PR/MR creation failed: ${CREATE_PR.variables.errorMessage}" From 381ecc63f54709e61daaeecab85135d3b658f31c Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Thu, 25 Jun 2026 10:55:48 +0530 Subject: [PATCH 42/46] Handling negative scenarios for CREATE_PR step --- .../main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml index e0d8c9fd855..bd616a2663f 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml @@ -219,6 +219,7 @@ steps: GIT_PUSH_CHANGES: cmd: ${#fcliCmd('GIT_PUSH_CHANGES', 'action run git-push-changes')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" + stdout: collect skip.if-reason: - ${#isBlank(remediationsApplied)?'Apply remediations was skipped or failed, skipping Git operations':''} on.fail: @@ -226,9 +227,11 @@ steps: CREATE_PR: cmd: ${#fcliCmd('CREATE_PR', 'action run create-pr')} "--source-dir=${global.ci.sourceDir}" "--source-branch=${GIT_PUSH_CHANGES.variables.branchName}" "--progress=none" + stdout: collect skip.if-reason: - ${GIT_PUSH_CHANGES.dependencySkipReason} - - "${GIT_PUSH_CHANGES.variables.status=='no_changes' ? 'No changes detected, skipping PR/MR creation' : ''}" + - ${#isBlank(GIT_PUSH_CHANGES.variables)?'No changes detected, skipping PR/MR creation':''} + - ${GIT_PUSH_CHANGES.variables.status!='success'?'Git push not completed/failed, skipping PR/MR creation':''} on.success: - if: ${CREATE_PR.variables.status=='success'} log.info: "Pull/Merge Request successfully created: ${CREATE_PR.variables.prLink}" From 9e7e907ad0877198afeccb7f104dfc5355a01b9d Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Thu, 25 Jun 2026 13:10:03 +0530 Subject: [PATCH 43/46] Branch name creation logic moved to ci.yaml to avoid redundant ineffective results storing --- .../actions/zip/git-push-changes.yaml | 20 +- .../helper/git/ActionGitSpelFunctions.java | 312 +++++++++--------- .../com/fortify/cli/ssc/actions/zip/ci.yaml | 11 +- 3 files changed, 180 insertions(+), 163 deletions(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-push-changes.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-push-changes.yaml index 98cfeaf8c7e..2f128071b72 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-push-changes.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-push-changes.yaml @@ -15,7 +15,7 @@ usage: Configuration via CLI options or environment variables: - `--source-dir` / `SOURCE_DIR` -- Directory where changes are detected (default: '.') - - `--branch-prefix` / `BRANCH_PREFIX` -- Branch name prefix (default: "fcli/aviator-remediations") + - `--branch-name` / `BRANCH_NAME` -- Full branch name to use. - `--commit-message` / `COMMIT_MESSAGE` -- Commit message - `--author-name` / `GIT_AUTHOR_NAME` -- Commit author name (default: "fcli-aviator[bot]") - `--author-email` / `GIT_AUTHOR_EMAIL` -- Commit author email @@ -38,12 +38,12 @@ cli.options: Defaults to current working directory if not specified. required: false default: "${#env('SOURCE_DIR')?:'.'}" - branchPrefix: - names: --branch-prefix + branchName: + names: --branch-name description: >- - Prefix for the new branch name. The branch name will be /. - required: false - default: "${#env('BRANCH_PREFIX')?:'fcli/aviator-remediations'}" + Full branch name to use. + required: true + default: "${#env('BRANCH_NAME')?:''}" commitMessage: names: --commit-message, -m description: >- @@ -64,7 +64,7 @@ cli.options: default: "${#env('GIT_AUTHOR_EMAIL')?:'fcli-aviator@opentext.com'}" steps: - - log.debug: "Action configuration: sourceDir=${cli.sourceDir}, branchPrefix=${cli.branchPrefix}, author=${cli.authorName} <${cli.authorEmail}>" + - log.debug: "Action configuration: sourceDir=${cli.sourceDir}, branchName=${cli.branchName}, author=${cli.authorName} <${cli.authorEmail}>" # Validate git repository - var.set: @@ -93,13 +93,13 @@ steps: # Create branch - var.set: - branchName: ${#git.createBranch(cli.sourceDir, cli.branchPrefix)} + branchName: "${#git.checkoutNewBranch(cli.sourceDir, cli.branchName)}" on.fail: - var.set: result.status: "error" result.errorMessage: "${lastException.message}" - log.warn: - msg: "Failed to create branch with prefix `'${cli.branchPrefix}`'" + msg: "Failed to create/checkout branch" cause: ${lastException} - throw: ${lastException} - log.info: "Created branch: ${branchName}" @@ -158,4 +158,4 @@ steps: - log.info: "git-stage-commit-push completed: branch=${result.branchName}, commit=${result.commitSha}" - out.write: - object: "${result}" \ No newline at end of file + object: "${result}" diff --git a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java index 8d6b768d1c1..b0d08499669 100644 --- a/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java +++ b/fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java @@ -59,7 +59,7 @@ public class ActionGitSpelFunctions { public static final ActionGitSpelFunctions INSTANCE = new ActionGitSpelFunctions(); - @SpelFunction(cat=util, desc=""" + @SpelFunction(cat = util, desc = """ Returns basic information about the local git repository for the given source directory, or null if the directory is not inside a git working tree. Only constant-time lookups are performed (HEAD commit only). Structure: @@ -73,38 +73,45 @@ public class ActionGitSpelFunctions { committer: { name, email, when } } } - """, returns="Git repository information or null if not a git work dir") + """, returns = "Git repository information or null if not a git work dir") public ObjectNode localRepo( - @SpelFunctionParam(name="sourceDir", desc="directory assumed to be inside a git working tree") String sourceDir) { - if (StringUtils.isBlank(sourceDir)) { return null; } + @SpelFunctionParam(name = "sourceDir", desc = "directory assumed to be inside a git working tree") String sourceDir) { + if (StringUtils.isBlank(sourceDir)) { + return null; + } var dir = Path.of(sourceDir).toAbsolutePath().normalize().toFile(); - if (!dir.exists()) { return null; } + if (!dir.exists()) { + return null; + } FileRepositoryBuilder builder = new FileRepositoryBuilder().findGitDir(dir); - if (builder.getGitDir() == null) { return null; } + if (builder.getGitDir() == null) { + return null; + } try (Repository repo = builder.build()) { var mapper = JsonHelper.getObjectMapper(); var remote = selectRemote(repo); var remoteUrl = remote == null ? "origin" : repo.getConfig().getString("remote", remote, "url"); var names = deriveRepoNames(dir.getName(), remoteUrl); var repository = CiRepository.builder() - .workspaceDir(repo.getWorkTree().getAbsolutePath()) - .remoteUrl(StringUtils.isBlank(remoteUrl) ? null : remoteUrl) - .name(CiRepositoryName.builder() - .short_(names[0]) - .full(names[1]) - .build()) - .build(); + .workspaceDir(repo.getWorkTree().getAbsolutePath()) + .remoteUrl(StringUtils.isBlank(remoteUrl) ? null : remoteUrl) + .name(CiRepositoryName.builder() + .short_(names[0]) + .full(names[1]) + .build()) + .build(); CiBranch branch = null; try { String fullBranch = repo.getFullBranch(); if (fullBranch != null) { branch = CiBranch.builder() - .full(fullBranch) - .short_(Repository.shortenRefName(fullBranch)) - .build(); + .full(fullBranch) + .short_(Repository.shortenRefName(fullBranch)) + .build(); } - } catch (Exception e) { /* ignore */ } + } catch (Exception e) { + /* ignore */ } CiCommit commit = null; var headId = repo.resolve("HEAD"); @@ -123,29 +130,30 @@ public ObjectNode localRepo( var committerIdent = gitCommit.getCommitterIdent(); var commitId = CiCommitId.builder() - .full(gitCommit.getId().getName()) - .short_(shortId) - .build(); + .full(gitCommit.getId().getName()) + .short_(shortId) + .build(); commit = CiCommit.builder() - .headId(commitId) - .mergeId(commitId) - .message(CiCommitMessage.builder() - .short_(gitCommit.getShortMessage()) - .full(gitCommit.getFullMessage()) - .build()) - .author(authorIdent != null ? CiPerson.builder() - .name(authorIdent.getName()) - .email(authorIdent.getEmailAddress()) - .when(authorIdent.getWhenAsInstant().toString()) - .build() : null) - .committer(committerIdent != null ? CiPerson.builder() - .name(committerIdent.getName()) - .email(committerIdent.getEmailAddress()) - .when(committerIdent.getWhenAsInstant().toString()) - .build() : null) - .build(); - } catch (Exception e) { /* ignore */ } + .headId(commitId) + .mergeId(commitId) + .message(CiCommitMessage.builder() + .short_(gitCommit.getShortMessage()) + .full(gitCommit.getFullMessage()) + .build()) + .author(authorIdent != null ? CiPerson.builder() + .name(authorIdent.getName()) + .email(authorIdent.getEmailAddress()) + .when(authorIdent.getWhenAsInstant().toString()) + .build() : null) + .committer(committerIdent != null ? CiPerson.builder() + .name(committerIdent.getName()) + .email(committerIdent.getEmailAddress()) + .when(committerIdent.getWhenAsInstant().toString()) + .build() : null) + .build(); + } catch (Exception e) { + /* ignore */ } } var root = mapper.createObjectNode(); @@ -157,45 +165,42 @@ public ObjectNode localRepo( root.set("commit", mapper.valueToTree(commit)); } return root; - } catch (Exception e) { - return null; + } catch (Exception e) { + return null; } } - @SpelFunction(cat=util, desc="Checks whether the working tree of the git repository at the given directory has any uncommitted changes (modified, added, or deleted files).", - returns="`true` if there are uncommitted changes, `false` otherwise or if not a git repository") + @SpelFunction(cat = util, desc = "Checks whether the working tree of the git repository at the given directory has any uncommitted changes (modified, added, or deleted files).", returns = "`true` if there are uncommitted changes, `false` otherwise or if not a git repository") public boolean hasChanges( - @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir) { + @SpelFunctionParam(name = "sourceDir", desc = "directory inside a git working tree") String sourceDir) { try (var git = openGit(sourceDir)) { - if (git == null) { return false; } + if (git == null) { + return false; + } var status = git.status().call(); boolean hasChanges = !status.getModified().isEmpty() - || !status.getAdded().isEmpty() - || !status.getRemoved().isEmpty() - || !status.getUntracked().isEmpty() - || !status.getChanged().isEmpty(); + || !status.getAdded().isEmpty() + || !status.getRemoved().isEmpty() + || !status.getUntracked().isEmpty() + || !status.getChanged().isEmpty(); return hasChanges; } catch (Exception e) { return false; } } - @SpelFunction(cat=util, desc="Creates a new branch in the local git repository and checks it out. The branch name is based on the provided prefix and a timestamp suffix to ensure uniqueness.", - returns="The name of the created branch") - public String createBranch( - @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir, - @SpelFunctionParam(name="branchPrefix", desc="prefix for the branch name (e.g., 'fcli/aviator-remediations')") String branchPrefix) { + @SpelFunction(cat = util, desc = "Creates a new branch with the given full name in the local git repository and checks it out.", returns = "The name of the created branch") + public String checkoutNewBranch( + @SpelFunctionParam(name = "sourceDir", desc = "directory inside a git working tree") String sourceDir, + @SpelFunctionParam(name = "branchName", desc = "full branch name to create and checkout") String branchName) { try (var git = openGit(sourceDir)) { if (git == null) { throw new FcliSimpleException("Not a git repository: " + sourceDir); } - var timestamp = java.time.LocalDateTime.now() - .format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss-SSS")); - var branchName = branchPrefix + "/" + timestamp; git.checkout() - .setCreateBranch(true) - .setName(branchName) - .call(); + .setCreateBranch(true) + .setName(branchName) + .call(); String current = git.getRepository().getBranch(); if (!branchName.equals(current)) { throw new FcliSimpleException("Failed to checkout branch " + branchName); @@ -206,10 +211,9 @@ public String createBranch( } } - @SpelFunction(cat=util, desc="Stages all modified and new files in the working tree for commit.", - returns="`true` if files were staged successfully") + @SpelFunction(cat = util, desc = "Stages all modified and new files in the working tree for commit.", returns = "`true` if files were staged successfully") public boolean addAll( - @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir) { + @SpelFunctionParam(name = "sourceDir", desc = "directory inside a git working tree") String sourceDir) { try (var git = openGit(sourceDir)) { if (git == null) { throw new FcliSimpleException("Not a git repository: " + sourceDir); @@ -221,13 +225,12 @@ public boolean addAll( } } - @SpelFunction(cat=util, desc="Commits all staged changes in the local git repository with the given message.", - returns="The commit SHA of the new commit") + @SpelFunction(cat = util, desc = "Commits all staged changes in the local git repository with the given message.", returns = "The commit SHA of the new commit") public String commit( - @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir, - @SpelFunctionParam(name="message", desc="commit message") String message, - @SpelFunctionParam(name="name", desc="commit author name") String name, - @SpelFunctionParam(name="email", desc="commit author email") String email) { + @SpelFunctionParam(name = "sourceDir", desc = "directory inside a git working tree") String sourceDir, + @SpelFunctionParam(name = "message", desc = "commit message") String message, + @SpelFunctionParam(name = "name", desc = "commit author name") String name, + @SpelFunctionParam(name = "email", desc = "commit author email") String email) { try (var git = openGit(sourceDir)) { if (git == null) { throw new FcliSimpleException("Not a git repository: " + sourceDir); @@ -238,22 +241,18 @@ public String commit( } var commitResult = git.commit() - .setMessage(message) - .setAuthor(name, email) - .setCommitter(name, email) - .call(); + .setMessage(message) + .setAuthor(name, email) + .setCommitter(name, email) + .call(); var sha = commitResult.getId().getName(); return sha; } catch (GitAPIException e) { throw new FcliSimpleException("Failed to commit: " + e.getMessage()); } } - - @SpelFunction( - cat = util, - desc = "Pushes the current branch to the remote repository. Uses token-based authentication from CI environment variables (GITHUB_TOKEN, CI_JOB_TOKEN, SYSTEM_ACCESSTOKEN, BITBUCKET_TOKEN) if available.", - returns = "The name of the remote ref that was pushed" - ) + + @SpelFunction(cat = util, desc = "Pushes the current branch to the remote repository. Uses token-based authentication from CI environment variables (GITHUB_TOKEN, CI_JOB_TOKEN, SYSTEM_ACCESSTOKEN, BITBUCKET_TOKEN) if available.", returns = "The name of the remote ref that was pushed") public String push( @SpelFunctionParam(name = "sourceDir", desc = "directory inside a git working tree") String sourceDir, @SpelFunctionParam(name = "branchName", desc = "name of the branch to push") String branchName) { @@ -263,15 +262,16 @@ public String push( } var repo = git.getRepository(); var remote = selectRemote(repo); - if (remote == null) remote = "origin"; + if (remote == null) + remote = "origin"; try { git.checkout().setName(branchName).call(); } catch (Exception e) { git.checkout() - .setCreateBranch(true) - .setName(branchName) - .setStartPoint("HEAD") - .call(); + .setCreateBranch(true) + .setName(branchName) + .setStartPoint("HEAD") + .call(); } var remoteUrl = repo.getConfig().getString("remote", remote, "url"); @@ -289,42 +289,38 @@ public String push( String fullBranchRef = "refs/heads/" + branchName; log.debug("PUSH DETAILS: branch={}, remote={}, remoteUrl={}, fullBranchRef={}", - branchName, - remote, - remoteUrl, - fullBranchRef - ); + branchName, + remote, + remoteUrl, + fullBranchRef); var refSpec = new RefSpec(fullBranchRef + ":" + fullBranchRef); if (credentialsProvider != null) { - log.debug("CREDENTIALS: type={}, class={}", - credentialsProvider.getClass().getSimpleName(), - credentialsProvider.getClass().getName() - ); + log.debug("CREDENTIALS: type={}, class={}", + credentialsProvider.getClass().getSimpleName(), + credentialsProvider.getClass().getName()); } log.debug("PUSH COMMAND SETUP: remote={}, refSpec={}, timeout=300s, credentialsSet={}", - remote, - refSpec.toString(), - credentialsProvider != null - ); + remote, + refSpec.toString(), + credentialsProvider != null); var fetchCmd = git.fetch().setRemote(remote); if (credentialsProvider != null) { fetchCmd.setCredentialsProvider(credentialsProvider); } - try{ + try { var fetchResult = fetchCmd.call(); - log.debug("Fetch completed with {} ref updates", - fetchResult != null ? fetchResult.getAdvertisedRefs().size() : 0 - ); - } catch(Exception e){ + log.debug("Fetch completed with {} ref updates", + fetchResult != null ? fetchResult.getAdvertisedRefs().size() : 0); + } catch (Exception e) { log.warn("Fetch failed (but continuing with push): {}", e.getMessage(), e); } var pushCmd = git.push() - .setRemote(remote) - .setRefSpecs(refSpec) - .setTimeout(300); + .setRemote(remote) + .setRefSpecs(refSpec) + .setTimeout(300); if (credentialsProvider != null) { pushCmd.setCredentialsProvider(credentialsProvider); @@ -332,8 +328,7 @@ public String push( var results = pushCmd.call(); log.debug("Push command completed. credentialsProvider: {}", - credentialsProvider != null ? credentialsProvider.getClass().getSimpleName() : "null" - ); + credentialsProvider != null ? credentialsProvider.getClass().getSimpleName() : "null"); // Don't convert to ArrayList - just iterate directly // We can't use .isEmpty() or .size() on Iterable, so remove those checks @@ -355,11 +350,10 @@ public String push( for (var update : result.getRemoteUpdates()) { var status = update.getStatus(); log.debug("Push update: status={}, remoteName={}, message='{}', forceUpdate={}", - status, - update.getRemoteName(), - update.getMessage() != null ? update.getMessage() : "null", - update.isForceUpdate() - ); + status, + update.getRemoteName(), + update.getMessage() != null ? update.getMessage() : "null", + update.isForceUpdate()); switch (status) { case OK: case UP_TO_DATE: @@ -375,11 +369,11 @@ public String push( case NOT_ATTEMPTED: default: throw new FcliSimpleException( - "Push rejected: " - + "status=" + status - + ", remote=" + update.getRemoteName() - + ", message=" + (update.getMessage() != null ? update.getMessage() : "no message") - ); + "Push rejected: " + + "status=" + status + + ", remote=" + update.getRemoteName() + + ", message=" + + (update.getMessage() != null ? update.getMessage() : "no message")); } } } @@ -390,7 +384,8 @@ public String push( } if (!success) { - throw new FcliSimpleException("Push completed but no refs were updated (likely auth or permission issue)"); + throw new FcliSimpleException( + "Push completed but no refs were updated (likely auth or permission issue)"); } return fullBranchRef; } catch (Exception e) { @@ -399,14 +394,12 @@ public String push( root = root.getCause(); } throw new FcliSimpleException( - "Failed to push (root cause): " + root.getClass().getName() + " - " + root.getMessage(), - e - ); + "Failed to push (root cause): " + root.getClass().getName() + " - " + root.getMessage(), + e); } } - @SpelFunction(cat=util, desc="Detects the repository owner from CI environment variables. Checks GITHUB_REPOSITORY_OWNER (GitHub), CI_PROJECT_NAMESPACE (GitLab), BUILD_REPOSITORY_ID (Azure DevOps), or BITBUCKET_WORKSPACE (Bitbucket). Returns null if not running in a supported CI system.", - returns="The repository owner/namespace or null if not detectable") + @SpelFunction(cat = util, desc = "Detects the repository owner from CI environment variables. Checks GITHUB_REPOSITORY_OWNER (GitHub), CI_PROJECT_NAMESPACE (GitLab), BUILD_REPOSITORY_ID (Azure DevOps), or BITBUCKET_WORKSPACE (Bitbucket). Returns null if not running in a supported CI system.", returns = "The repository owner/namespace or null if not detectable") public String ciRepositoryOwner() { var owner = EnvHelper.env("GITHUB_REPOSITORY_OWNER"); if (StringUtils.isNotBlank(owner)) { @@ -428,16 +421,17 @@ public String ciRepositoryOwner() { return null; } - @SpelFunction(cat=util, desc="Detects the default branch of the remote repository. Checks CI environment variables (CI_DEFAULT_BRANCH for GitLab, looks up via GitHub API env), then falls back to reading refs/remotes/origin/HEAD from the local git config. Returns null if detection fails.", - returns="The default branch name (e.g. 'main', 'master', 'develop') or null if not detectable") + @SpelFunction(cat = util, desc = "Detects the default branch of the remote repository. Checks CI environment variables (CI_DEFAULT_BRANCH for GitLab, looks up via GitHub API env), then falls back to reading refs/remotes/origin/HEAD from the local git config. Returns null if detection fails.", returns = "The default branch name (e.g. 'main', 'master', 'develop') or null if not detectable") public String defaultBranch( - @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir) { + @SpelFunctionParam(name = "sourceDir", desc = "directory inside a git working tree") String sourceDir) { var defaultBranch = EnvHelper.env("CI_DEFAULT_BRANCH"); if (StringUtils.isNotBlank(defaultBranch)) { return defaultBranch; } try (var git = openGit(sourceDir)) { - if (git == null) { return null; } + if (git == null) { + return null; + } var repo = git.getRepository(); var remoteHead = repo.resolve("refs/remotes/origin/HEAD"); if (remoteHead != null) { @@ -455,22 +449,24 @@ public String defaultBranch( return null; } - - @SpelFunction(cat=util, desc=""" + @SpelFunction(cat = util, desc = """ Detects the hosting platform of the repository by parsing the git remote URL. Returns "github" for GitHub-hosted repositories (github.com or *.github.com), "gitlab" for GitLab-hosted repositories (gitlab.com or hostnames containing "gitlab"), and "unknown" for any other remote or when detection fails. This is platform detection (where the repo lives), not CI detection (where the pipeline runs). - """, - returns="\"github\", \"gitlab\", or \"unknown\"") + """, returns = "\"github\", \"gitlab\", or \"unknown\"") public String repositoryPlatform( - @SpelFunctionParam(name="sourceDir", desc="directory inside a git working tree") String sourceDir) { + @SpelFunctionParam(name = "sourceDir", desc = "directory inside a git working tree") String sourceDir) { try (var git = openGit(sourceDir)) { - if (git == null) { return "unknown"; } + if (git == null) { + return "unknown"; + } var repo = git.getRepository(); var remote = selectRemote(repo); - if (remote == null) { return "unknown"; } + if (remote == null) { + return "unknown"; + } var remoteUrl = repo.getConfig().getString("remote", remote, "url"); return detectPlatformFromUrl(remoteUrl); } catch (Exception e) { @@ -480,12 +476,18 @@ public String repositoryPlatform( } private Git openGit(String sourceDir) { - if (StringUtils.isBlank(sourceDir)) { return null; } + if (StringUtils.isBlank(sourceDir)) { + return null; + } try { var dir = Path.of(sourceDir).toAbsolutePath().normalize().toFile(); - if (!dir.exists()) { return null; } + if (!dir.exists()) { + return null; + } var builder = new FileRepositoryBuilder().findGitDir(dir); - if (builder.getGitDir() == null) { return null; } + if (builder.getGitDir() == null) { + return null; + } return new Git(builder.build()); } catch (Exception e) { return null; @@ -495,7 +497,8 @@ private Git openGit(String sourceDir) { private static String selectRemote(Repository repo) { try { var remotes = repo.getRemoteNames(); - if (remotes == null || remotes.isEmpty()) return null; + if (remotes == null || remotes.isEmpty()) + return null; return remotes.contains("origin") ? "origin" : remotes.iterator().next(); } catch (Exception e) { return null; @@ -503,10 +506,14 @@ private static String selectRemote(Repository repo) { } private static String[] deriveRepoNames(String fallbackShort, String remoteUrl) { - if (StringUtils.isBlank(remoteUrl)) { return new String[]{fallbackShort, null}; } + if (StringUtils.isBlank(remoteUrl)) { + return new String[] { fallbackShort, null }; + } try { var cleaned = remoteUrl.trim(); - if (cleaned.endsWith(".git")) { cleaned = cleaned.substring(0, cleaned.length() - 4); } + if (cleaned.endsWith(".git")) { + cleaned = cleaned.substring(0, cleaned.length() - 4); + } String pathPart; if (cleaned.startsWith("git@")) { int idx = cleaned.indexOf(":"); @@ -514,20 +521,25 @@ private static String[] deriveRepoNames(String fallbackShort, String remoteUrl) } else { var uri = java.net.URI.create(cleaned); pathPart = uri.getPath(); - if (pathPart.startsWith("/")) { pathPart = pathPart.substring(1); } + if (pathPart.startsWith("/")) { + pathPart = pathPart.substring(1); + } } var parts = pathPart.split("/"); if (parts.length >= 2) { var shortName = parts[parts.length - 1]; - return new String[]{shortName, pathPart}; + return new String[] { shortName, pathPart }; } - return new String[]{parts[parts.length - 1], null}; + return new String[] { parts[parts.length - 1], null }; } catch (Exception e) { - return new String[]{fallbackShort, null}; + return new String[] { fallbackShort, null }; } } + private static String detectPlatformFromUrl(String remoteUrl) { - if (StringUtils.isBlank(remoteUrl)) { return "unknown"; } + if (StringUtils.isBlank(remoteUrl)) { + return "unknown"; + } try { String host; var cleaned = remoteUrl.trim(); @@ -539,10 +551,16 @@ private static String detectPlatformFromUrl(String remoteUrl) { } else { host = java.net.URI.create(cleaned).getHost(); } - if (host == null) { return "unknown"; } + if (host == null) { + return "unknown"; + } host = host.toLowerCase(); - if (host.equals("github.com") || host.endsWith(".github.com")) { return "github"; } - if (host.equals("gitlab.com") || host.contains("gitlab")) { return "gitlab"; } + if (host.equals("github.com") || host.endsWith(".github.com")) { + return "github"; + } + if (host.equals("gitlab.com") || host.contains("gitlab")) { + return "gitlab"; + } } catch (Exception e) { log.debug("Failed to parse remote URL for platform detection: {}", remoteUrl); } diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml index bd616a2663f..0d0cc34a564 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml @@ -215,23 +215,22 @@ steps: - ${aviator.skipReason} # Skip if Aviator is not configured - ${AVIATOR_WAIT.dependencySkipReason} # Skip if AVIATOR_WAIT was skipped or failed (no remediations available) on.success: - - var.set: { remediationsApplied: 'true' } + - var.set: + remediationsApplied: 'true' + branchName: "aviator/remediations/${#formatDateTime('yyyyMMdd-HHmmss-SSS')}" GIT_PUSH_CHANGES: - cmd: ${#fcliCmd('GIT_PUSH_CHANGES', 'action run git-push-changes')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" - stdout: collect + cmd: ${#fcliCmd('GIT_PUSH_CHANGES', 'action run git-push-changes')} "--source-dir=${global.ci.sourceDir}" "--branch-name=${branchName}" "--progress=none" skip.if-reason: - ${#isBlank(remediationsApplied)?'Apply remediations was skipped or failed, skipping Git operations':''} on.fail: - log.warn: "Git stage/commit/push failed" CREATE_PR: - cmd: ${#fcliCmd('CREATE_PR', 'action run create-pr')} "--source-dir=${global.ci.sourceDir}" "--source-branch=${GIT_PUSH_CHANGES.variables.branchName}" "--progress=none" + cmd: ${#fcliCmd('CREATE_PR', 'action run create-pr')} "--source-dir=${global.ci.sourceDir}" "--source-branch=${branchName}" "--progress=none" stdout: collect skip.if-reason: - ${GIT_PUSH_CHANGES.dependencySkipReason} - - ${#isBlank(GIT_PUSH_CHANGES.variables)?'No changes detected, skipping PR/MR creation':''} - - ${GIT_PUSH_CHANGES.variables.status!='success'?'Git push not completed/failed, skipping PR/MR creation':''} on.success: - if: ${CREATE_PR.variables.status=='success'} log.info: "Pull/Merge Request successfully created: ${CREATE_PR.variables.prLink}" From 69c460fcee3b4b9f229102ce44f934ff0ce8b3eb Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Thu, 25 Jun 2026 14:32:26 +0530 Subject: [PATCH 44/46] Logging the error scenario, removed success logs --- .../com/fortify/cli/ssc/actions/zip/ci.yaml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml index 0d0cc34a564..54b1eded856 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml @@ -228,18 +228,10 @@ steps: CREATE_PR: cmd: ${#fcliCmd('CREATE_PR', 'action run create-pr')} "--source-dir=${global.ci.sourceDir}" "--source-branch=${branchName}" "--progress=none" - stdout: collect skip.if-reason: - ${GIT_PUSH_CHANGES.dependencySkipReason} - on.success: - - if: ${CREATE_PR.variables.status=='success'} - log.info: "Pull/Merge Request successfully created: ${CREATE_PR.variables.prLink}" - - if: ${CREATE_PR.variables.status=='no_changes'} - log.info: "No changes detected - PR/MR creation skipped" - - if: ${CREATE_PR.variables.status=='no_pr_support'} - log.warn: "Branch created and pushed, but PR/MR creation not supported for platform '${CREATE_PR.variables.platform}'" - - if: ${CREATE_PR.variables.status=='error'} - log.warn: "PR/MR creation failed: ${CREATE_PR.variables.errorMessage}" + on.fail: + - log.warn: "PR/MR creation failed" CHECK_POLICY: cmd: ${#actionCmd('CHECK_POLICY', 'ssc', 'check-policy')} "--av=${global.ci.av}" "--progress=none" From 1dc1e3a18da687da4b449b526ae1880272141f43 Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Thu, 25 Jun 2026 22:18:43 +0530 Subject: [PATCH 45/46] Code cleanup --- .../generic_action/actions/zip/create-pr.yaml | 50 ++----------------- .../actions/zip/git-push-changes.yaml | 37 +------------- .../com/fortify/cli/fod/actions/zip/ci.yaml | 26 ++++------ .../com/fortify/cli/ssc/actions/zip/ci.yaml | 7 ++- 4 files changed, 18 insertions(+), 102 deletions(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml index 76762d5fa20..bf47322949c 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml @@ -21,11 +21,6 @@ usage: - `--title` / `PR_TITLE` -- PR/MR title - `--body` / `PR_BODY` -- PR/MR description - Output variables: - - `result.status` -- "success", "no_pr_support", or "error" - - `result.prLink` -- URL of the created PR/MR (empty on failure) - - `result.platform` -- Detected repository platform ("github", "gitlab", or "unknown") - config: output: immediate @@ -65,26 +60,15 @@ cli.options: steps: - if: "${#isBlank(cli.sourceBranch)}" - throw: "Option '--source-branch' is required. Pass the branch created by git-stage-commit-push." + throw: "Option '--source-branch' is required. Pass the source branch from which the PR/MR is to be created." # Compute base branch: CLI option/env var -> git remote HEAD -> "main" - var.set: baseBranch: "${#ifBlank(cli.baseBranch, #ifBlank(#git.defaultBranch(cli.sourceDir), 'main'))}" - - log.debug: "Computed base branch for PR/MR: ${baseBranch}" - - - log.debug: "Started the action create PR/MR: for base branch '${baseBranch}' and source branch '${cli.sourceBranch}'" - - - log.debug: "PR/MR configuration: platform detection from '${cli.sourceDir}', source=${cli.sourceBranch}, base=${baseBranch}" # Detect repository platform from git remote URL - var.set: repoPlatform: ${#git.repositoryPlatform(cli.sourceDir)} - - log.debug: "Detected repository platform: ${repoPlatform}" - - - var.set: - result.status: "error" - result.prLink: "" - result.platform: "${repoPlatform}" # GitHub PR creation - if: "${repoPlatform=='github'}" @@ -101,25 +85,15 @@ steps: cause: ${lastException} - log.warn: "Troubleshooting: Verify branch '${cli.sourceBranch}' exists on remote and base branch '${baseBranch}' is valid" - log.debug: "Failed to create GitHub PR: ${lastException.message}" - - var.set: - result.status: "error" - result.errorMessage: "${lastException.message}" - throw: msg: "Failed to create GitHub Pull Request from '${cli.sourceBranch}' to '${baseBranch}'" cause: ${lastException} - if: ${#isNotBlank(pr)} && ${#isNotBlank(pr.html_url)} && ${#isNotBlank(pr.number)} - do: - - var.set: - result.status: "success" - result.prLink: "${pr.html_url}" - - log.info: "Created GitHub Pull Request #${pr.number}: ${pr.html_url}" + - log.info: "Created GitHub Pull Request #${pr.number}: ${pr.html_url}" - if: ${#isBlank(pr)} || ${#isBlank(pr.html_url)} do: - log.warn: "PR creation returned empty or invalid response: ${pr}" - - var.set: - result.status: "error" - result.errorMessage: "GitHub PR creation did not return expected response. Check logs for details." - throw: "GitHub PR creation did not return expected response. Check logs for details." # GitLab MR creation - if: "${repoPlatform=='gitlab'}" @@ -132,37 +106,21 @@ steps: msg: "Failed to create GitLab Merge Request from '${cli.sourceBranch}' to '${baseBranch}'" cause: ${lastException} - log.warn: "Troubleshooting: Verify branch '${cli.sourceBranch}' exists on remote and base branch '${baseBranch}' is valid" - - var.set: - result.status: "error" - result.errorMessage: "${lastException.message}" - throw: msg: "Failed to create GitLab Merge Request from '${cli.sourceBranch}' to '${baseBranch}'" cause: ${lastException} - if: ${#isNotBlank(mr)} && ${#isNotBlank(mr.web_url)} && ${#isNotBlank(mr.iid)} do: - - var.set: - result.status: "success" - result.prLink: "${mr.web_url}" - log.info: "Created GitLab Merge Request !${mr.iid}: ${mr.web_url}" - if: ${#isBlank(mr)} || ${#isBlank(mr.web_url)} do: - log.warn: "Merge Request creation returned empty or invalid response: ${mr}" - - var.set: - result.status: "error" - result.errorMessage: "GitLab MR creation did not return expected response. Check logs for details." - throw: "GitLab MR creation did not return expected response. Check logs for details." # Unsupported repository platform - if: "${repoPlatform!='github' && repoPlatform!='gitlab'}" do: - log.warn: "PR/MR creation is not supported for repository platform '${repoPlatform}'. Branch '${cli.sourceBranch}' has been pushed; please create a PR/MR manually." - - var.set: - result.status: "no_pr_support" - - - log.info: "pr-or-mr-create completed: status=${result.status}, platform=${result.platform}" - - if: "${result.status=='success'}" - log.info: "PR/MR URL: ${result.prLink}" - - - out.write: - object: "${result}" \ No newline at end of file + - throw: "PR/MR creation is not supported for repository platform '${repoPlatform}'" + - log.info: "create-pr completed: PR/MR created for branch '${cli.sourceBranch}' targeting '${baseBranch}'" diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-push-changes.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-push-changes.yaml index 2f128071b72..282e1819b27 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-push-changes.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/git-push-changes.yaml @@ -20,12 +20,6 @@ usage: - `--author-name` / `GIT_AUTHOR_NAME` -- Commit author name (default: "fcli-aviator[bot]") - `--author-email` / `GIT_AUTHOR_EMAIL` -- Commit author email - Output variables (accessible by subsequent actions or scripts): - - `result.status` -- "success", "no_changes", or "error" - - `result.branchName` -- Name of the newly created branch - - `result.commitSha` -- SHA of the created commit - - `result.pushedRef` -- Full ref pushed to the remote - config: output: immediate @@ -81,23 +75,13 @@ steps: - if: ${!hasChanges} do: - log.info: "No changes detected in ${cli.sourceDir}, skipping commit and push." - - var.set: - result.status: "no_changes" - result.branchName: "" - result.commitSha: "" - result.pushedRef: "" - - out.write: - object: "${result}" - exit: 0 - log.info: "Changes detected in ${cli.sourceDir}, proceeding." - # Create branch + # Create and checkout branch - var.set: branchName: "${#git.checkoutNewBranch(cli.sourceDir, cli.branchName)}" on.fail: - - var.set: - result.status: "error" - result.errorMessage: "${lastException.message}" - log.warn: msg: "Failed to create/checkout branch" cause: ${lastException} @@ -108,9 +92,6 @@ steps: - var.set: staged: ${#git.addAll(cli.sourceDir)} on.fail: - - var.set: - result.status: "error" - result.errorMessage: "${lastException.message}" - log.warn: msg: "Failed to stage changes in ${cli.sourceDir}" cause: ${lastException} @@ -121,9 +102,6 @@ steps: - var.set: commitSha: "${#git.commit(cli.sourceDir, cli.commitMessage, cli.authorName, cli.authorEmail)}" on.fail: - - var.set: - result.status: "error" - result.errorMessage: "${lastException.message}" - log.warn: msg: "Failed to commit staged changes" cause: ${lastException} @@ -136,8 +114,6 @@ steps: pushedRef: ${#git.push(cli.sourceDir, branchName)} on.fail: - var.set: - result.status: "error" - result.errorMessage: "${lastException.message}" remoteUrl: "${gitRepoInfo.repository.remoteUrl?:'unknown'}" exceptionMsg: "${lastException.message}" - log.warn: @@ -149,13 +125,4 @@ steps: msg: "Failed to push branch '${branchName}' to remote" cause: ${lastException} - log.info: "Pushed branch to remote: ${pushedRef}" - - - var.set: - result.status: "success" - result.branchName: "${branchName}" - result.commitSha: "${commitSha}" - result.pushedRef: "${pushedRef}" - - log.info: "git-stage-commit-push completed: branch=${result.branchName}, commit=${result.commitSha}" - - - out.write: - object: "${result}" + - log.info: "git-push-changes completed: branch=${branchName}, commit=${commitSha}" diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml index 02c50cf8af2..e20e2624ce0 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml @@ -143,29 +143,25 @@ steps: - ${#env('DO_AVIATOR_AUDIT')!='true'?'Aviator audit not enabled (DO_AVIATOR_AUDIT!=true), no remediations available':''} # Skip if Aviator audit was not enabled - ${SAST_WAIT.dependencySkipReason} # Skip if SAST scan/wait was skipped or failed on.success: - - var.set: { remediationsApplied: 'true' } + - var.set: + remediationsApplied: 'true' + branchName: "aviator/remediations/${#formatDateTime('yyyyMMdd-HHmmss-SSS')}" GIT_PUSH_CHANGES: - cmd: ${#fcliCmd('GIT_PUSH_CHANGES', 'action run git-push-changes')} "--source-dir=${global.ci.sourceDir}" "--branch-prefix=aviator/remediations" "--progress=none" + cmd: ${#fcliCmd('GIT_PUSH_CHANGES', 'action run git-push-changes')} "--source-dir=${global.ci.sourceDir}" "--branch-name=${branchName}" "--progress=none" skip.if-reason: - ${#isBlank(remediationsApplied)?'Apply remediations was skipped or failed, skipping Git operations':''} on.fail: - log.warn: "Git stage/commit/push failed" CREATE_PR: - cmd: ${#fcliCmd('CREATE_PR', 'action run create-pr')} "--source-dir=${global.ci.sourceDir}" "--source-branch=${GIT_PUSH_CHANGES.variables.branchName}" "--progress=none" + cmd: ${#fcliCmd('CREATE_PR', 'action run create-pr')} "--source-dir=${global.ci.sourceDir}" "--source-branch=${branchName}" "--progress=none" skip.if-reason: - ${GIT_PUSH_CHANGES.dependencySkipReason} - - "${GIT_PUSH_CHANGES.variables.status=='no_changes' ? 'No changes detected, skipping PR/MR creation' : ''}" - on.success: - - if: ${CREATE_PR.variables.status=='success'} - log.info: "Pull/Merge Request successfully created: ${CREATE_PR.variables.prLink}" - - if: ${CREATE_PR.variables.status=='no_changes'} - log.info: "No changes detected - PR/MR creation skipped" - - if: ${CREATE_PR.variables.status=='no_pr_support'} - log.warn: "Branch created and pushed, but PR/MR creation not supported for platform '${CREATE_PR.variables.platform}'" - - if: ${CREATE_PR.variables.status=='error'} - log.warn: "PR/MR creation failed: ${CREATE_PR.variables.errorMessage}" + on.fail: + - log.warn: + msg: "PR/MR creation failed" + cause: ${lastException} CHECK_POLICY: cmd: ${#actionCmd('CHECK_POLICY', 'fod', 'check-policy')} "--rel=${global.ci.rel}" "--progress=none" @@ -239,7 +235,3 @@ formatters: ${CHECK_POLICY.exitCode==0||CHECK_POLICY.exitCode==100?CHECK_POLICY.stdout:CHECK_POLICY.dependencySkipReason} ${RELEASE_SUMMARY.stdout} - - - - diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml index 54b1eded856..6651149069c 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml @@ -231,7 +231,9 @@ steps: skip.if-reason: - ${GIT_PUSH_CHANGES.dependencySkipReason} on.fail: - - log.warn: "PR/MR creation failed" + - log.warn: + msg: "PR/MR creation failed" + cause: ${lastException} CHECK_POLICY: cmd: ${#actionCmd('CHECK_POLICY', 'ssc', 'check-policy')} "--av=${global.ci.av}" "--progress=none" @@ -312,6 +314,3 @@ formatters: ${CHECK_POLICY.exitCode==0||CHECK_POLICY.exitCode==100?CHECK_POLICY.stdout:CHECK_POLICY.dependencySkipReason} ${APPVERSION_SUMMARY.stdout} - - - From 455537f41a70ed521c8c65040e30216ff0607d9d Mon Sep 17 00:00:00 2001 From: Sangamesh Vijaykumar Date: Thu, 25 Jun 2026 22:19:05 +0530 Subject: [PATCH 46/46] Code cleanup --- .../com/fortify/cli/generic_action/actions/zip/create-pr.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml index bf47322949c..5455cef49d1 100644 --- a/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml +++ b/fcli-core/fcli-action/src/main/resources/com/fortify/cli/generic_action/actions/zip/create-pr.yaml @@ -89,7 +89,8 @@ steps: msg: "Failed to create GitHub Pull Request from '${cli.sourceBranch}' to '${baseBranch}'" cause: ${lastException} - if: ${#isNotBlank(pr)} && ${#isNotBlank(pr.html_url)} && ${#isNotBlank(pr.number)} - - log.info: "Created GitHub Pull Request #${pr.number}: ${pr.html_url}" + do: + - log.info: "Created GitHub Pull Request #${pr.number}: ${pr.html_url}" - if: ${#isBlank(pr)} || ${#isBlank(pr.html_url)} do: