From 8647e07efc68775c7f78babeb8005787dd329d8f Mon Sep 17 00:00:00 2001 From: david karapetyan Date: Mon, 13 Jul 2015 11:43:43 -0700 Subject: [PATCH 01/54] fixing PSQLException error --- .../config/ConfigurationPersistenceImpl.java | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java index 9d069bd..636eb0c 100644 --- a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java +++ b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java @@ -469,26 +469,38 @@ public PullRequestMetadata getPullRequestMetadata(PullRequest pr) { */ @Override public PullRequestMetadata getPullRequestMetadata(int repoId, Long prId, String fromSha, String toSha) { - // We have to check repoId being equal to -1 so that this works with old data. - PullRequestMetadata[] prms = ao.find(PullRequestMetadata.class, - "(REPO_ID = ? OR REPO_ID = -1) AND PULL_REQUEST_ID = ? and TO_SHA = ? and FROM_SHA = ?", repoId, prId, - toSha, fromSha); - if (prms.length == 0) { + int count = 0; + int retries = 3; + while (true) { + try { + // We have to check repoId being equal to -1 so that this works with old data. + PullRequestMetadata[] prms = ao.find(PullRequestMetadata.class, + "(REPO_ID = ? OR REPO_ID = -1) AND PULL_REQUEST_ID = ? and TO_SHA = ? and FROM_SHA = ?", repoId, prId, + toSha, fromSha); + if (prms.length == 0) { // new/updated PR, create a new object log.info("Creating PR Metadata for pull request: repo id:" + repoId + "pr id: " + prId + ", fromSha: " + fromSha + ", toSha: " + toSha); PullRequestMetadata prm = - ao.create( - PullRequestMetadata.class, - new DBParam("REPO_ID", repoId), - new DBParam("PULL_REQUEST_ID", prId), - new DBParam("TO_SHA", toSha), - new DBParam("FROM_SHA", fromSha)); + ao.create( + PullRequestMetadata.class, + new DBParam("REPO_ID", repoId), + new DBParam("PULL_REQUEST_ID", prId), + new DBParam("TO_SHA", toSha), + new DBParam("FROM_SHA", fromSha)); prm.save(); return prm; + } + return prms[0]; + } catch (Exception e) { + count = count + 1; + Thread.sleep(5000); + if (count > retries) { + throw e; + } } - return prms[0]; + } } /* (non-Javadoc) From cd2074f423034e9fb0be6a1fe79ede99d4cd07f0 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Tue, 1 Sep 2015 14:16:28 -0700 Subject: [PATCH 02/54] Revert "Merge pull request #24 from davidk01/psql-retry" This reverts commit 0e833f7760f4a61cf6b6b35ea74097e4fcb0af98, reversing changes made to 123e70cbcbe21cd999899d72a265f4b5694b1177. --- .../config/ConfigurationPersistenceImpl.java | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java index 636eb0c..9d069bd 100644 --- a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java +++ b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java @@ -469,38 +469,26 @@ public PullRequestMetadata getPullRequestMetadata(PullRequest pr) { */ @Override public PullRequestMetadata getPullRequestMetadata(int repoId, Long prId, String fromSha, String toSha) { - int count = 0; - int retries = 3; - while (true) { - try { - // We have to check repoId being equal to -1 so that this works with old data. - PullRequestMetadata[] prms = ao.find(PullRequestMetadata.class, - "(REPO_ID = ? OR REPO_ID = -1) AND PULL_REQUEST_ID = ? and TO_SHA = ? and FROM_SHA = ?", repoId, prId, - toSha, fromSha); - if (prms.length == 0) { + // We have to check repoId being equal to -1 so that this works with old data. + PullRequestMetadata[] prms = ao.find(PullRequestMetadata.class, + "(REPO_ID = ? OR REPO_ID = -1) AND PULL_REQUEST_ID = ? and TO_SHA = ? and FROM_SHA = ?", repoId, prId, + toSha, fromSha); + if (prms.length == 0) { // new/updated PR, create a new object log.info("Creating PR Metadata for pull request: repo id:" + repoId + "pr id: " + prId + ", fromSha: " + fromSha + ", toSha: " + toSha); PullRequestMetadata prm = - ao.create( - PullRequestMetadata.class, - new DBParam("REPO_ID", repoId), - new DBParam("PULL_REQUEST_ID", prId), - new DBParam("TO_SHA", toSha), - new DBParam("FROM_SHA", fromSha)); + ao.create( + PullRequestMetadata.class, + new DBParam("REPO_ID", repoId), + new DBParam("PULL_REQUEST_ID", prId), + new DBParam("TO_SHA", toSha), + new DBParam("FROM_SHA", fromSha)); prm.save(); return prm; - } - return prms[0]; - } catch (Exception e) { - count = count + 1; - Thread.sleep(5000); - if (count > retries) { - throw e; - } } - } + return prms[0]; } /* (non-Javadoc) From ecf55e2e3a1e3178df8267e3eb25ec195fda920c Mon Sep 17 00:00:00 2001 From: Andrew Karnani Date: Thu, 8 Oct 2015 15:31:52 -0700 Subject: [PATCH 03/54] Add a prefix option that allows jobs to be put in different folders --- .../config/ConfigurationPersistenceImpl.java | 1198 +++++++++-------- .../ConfigurationPersistenceService.java | 261 ++-- .../managers/JenkinsClientManager.java | 21 +- .../stashbot/managers/JenkinsManager.java | 969 ++++++------- .../JenkinsServerConfiguration.java | 322 +++-- .../JenkinsServerConfigurationImpl.java | 34 +- .../servlet/BuildSuccessReportingServlet.java | 596 ++++---- .../static/jenkins-configuration-panel.soy | 12 + .../stashbot/config/ConfigurationTest.java | 1 + .../managers/JenkinsClientManagerTest.java | 59 +- .../stashbot/managers/JenkinsManagerTest.java | 333 ++--- 11 files changed, 2041 insertions(+), 1765 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java index 9d069bd..935d670 100644 --- a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java +++ b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java @@ -39,530 +39,676 @@ import com.palantir.stash.stashbot.persistence.PullRequestMetadata; import com.palantir.stash.stashbot.persistence.RepositoryConfiguration; -public class ConfigurationPersistenceImpl implements ConfigurationPersistenceService { - - private final ActiveObjects ao; - private final Logger log; - private final EventPublisher publisher; - - private static final String DEFAULT_JENKINS_SERVER_CONFIG_KEY = "default"; - - public ConfigurationPersistenceImpl(ActiveObjects ao, PluginLoggerFactory lf, EventPublisher publisher) { - this.ao = ao; - this.log = lf.getLoggerForThis(this); - this.publisher = publisher; - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#deleteJenkinsServerConfiguration(java.lang.String) - */ - @Override - public void deleteJenkinsServerConfiguration(String name) { - JenkinsServerConfiguration[] configs = ao.find( - JenkinsServerConfiguration.class, - Query.select().where("NAME = ?", name)); - if (configs.length == 0) { - return; - } - for (JenkinsServerConfiguration jsc : configs) { - ao.delete(jsc); - } - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#getJenkinsServerConfiguration(java.lang.String) - */ - @Override - public JenkinsServerConfiguration getJenkinsServerConfiguration(String name) - throws SQLException { - if (name == null) { - name = DEFAULT_JENKINS_SERVER_CONFIG_KEY; - } - JenkinsServerConfiguration[] configs = ao.find( - JenkinsServerConfiguration.class, - Query.select().where("NAME = ?", name)); - if (configs.length == 0) { - // just use the defaults - return ao.create(JenkinsServerConfiguration.class, new DBParam( - "NAME", name)); - } - - String url = configs[0].getUrl(); - if (url.endsWith("/")) { - url = url.substring(0, url.length() - 1); - configs[0].setUrl(url); - configs[0].save(); - } - return configs[0]; - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#setJenkinsServerConfigurationFromRequest(javax.servlet.http.HttpServletRequest) - */ - @Override - public void setJenkinsServerConfigurationFromRequest(HttpServletRequest req) throws SQLException, - NumberFormatException { - - String name = req.getParameter("name"); - String url = req.getParameter("url"); - String username = req.getParameter("username"); - String password = req.getParameter("password"); - AuthenticationMode am = AuthenticationMode.fromMode(req.getParameter("authenticationMode")); - String stashUsername = req.getParameter("stashUsername"); - String stashPassword = req.getParameter("stashPassword"); - Integer maxVerifyChain = Integer.parseInt(req.getParameter("maxVerifyChain")); - String lockStr = req.getParameter("locked"); - Boolean isLocked = (lockStr == null || !lockStr.equals("on")) ? false : true; - - setJenkinsServerConfiguration(name, url, username, password, am, stashUsername, stashPassword, maxVerifyChain, - isLocked); - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#setJenkinsServerConfiguration(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.Integer) - */ - @Override - @Deprecated - public void setJenkinsServerConfiguration(String name, String url, - String username, String password, String stashUsername, String stashPassword, Integer maxVerifyChain) - throws SQLException { - setJenkinsServerConfiguration(name, url, username, password, AuthenticationMode.USERNAME_AND_PASSWORD, - stashUsername, stashPassword, maxVerifyChain, false); - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#setJenkinsServerConfiguration(java.lang.String, java.lang.String, java.lang.String, java.lang.String, com.palantir.stash.stashbot.config.JenkinsServerConfiguration.AuthenticationMode, java.lang.String, java.lang.String, java.lang.Integer, java.lang.Boolean) - */ - @Override - public void setJenkinsServerConfiguration(String name, String url, - String username, String password, AuthenticationMode authenticationMode, String stashUsername, - String stashPassword, Integer maxVerifyChain, Boolean isLocked) - throws SQLException { - if (name == null) { - name = DEFAULT_JENKINS_SERVER_CONFIG_KEY; - } - validateName(name); - JenkinsServerConfiguration[] configs = ao.find( - JenkinsServerConfiguration.class, - Query.select().where("NAME = ?", name)); - - if (configs.length == 0) { - log.info("Creating jenkins configuration: " + name); - ao.create(JenkinsServerConfiguration.class, new DBParam("NAME", - name), new DBParam("URL", url), new DBParam("USERNAME", - username), new DBParam("PASSWORD", password), new DBParam( - "STASH_USERNAME", stashUsername), new DBParam( - "STASH_PASSWORD", stashPassword), new DBParam( - "MAX_VERIFY_CHAIN", maxVerifyChain), new DBParam("LOCKED", isLocked)); - return; - } - // already exists, so update it - configs[0].setName(name); - configs[0].setUrl(url); - configs[0].setUsername(username); - configs[0].setPassword(password); - configs[0].setAuthenticationMode(authenticationMode); - configs[0].setStashUsername(stashUsername); - configs[0].setStashPassword(stashPassword); - configs[0].setMaxVerifyChain(maxVerifyChain); - configs[0].setLocked(isLocked); - configs[0].save(); - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#getRepositoryConfigurationForRepository(com.atlassian.stash.repository.Repository) - */ - @Override - public RepositoryConfiguration getRepositoryConfigurationForRepository( - Repository repo) throws SQLException { - RepositoryConfiguration[] repos = ao.find( - RepositoryConfiguration.class, - Query.select().where("REPO_ID = ?", repo.getId())); - if (repos.length == 0) { - // just use the defaults - RepositoryConfiguration rc = ao.create( - RepositoryConfiguration.class, - new DBParam("REPO_ID", repo.getId())); - rc.save(); - // default the 3 base job types to enabled - setJobTypeStatusMapping(rc, JobType.VERIFY_COMMIT, true); - setJobTypeStatusMapping(rc, JobType.VERIFY_PR, true); - setJobTypeStatusMapping(rc, JobType.PUBLISH, true); - return rc; - } - return repos[0]; - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#setRepositoryConfigurationForRepository(com.atlassian.stash.repository.Repository, boolean, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean) - */ - @Override - public void setRepositoryConfigurationForRepository(Repository repo, - boolean isCiEnabled, String verifyBranchRegex, - String verifyBuildCommand, String publishBranchRegex, - String publishBuildCommand, String prebuildCommand, boolean rebuildOnUpdate) - throws SQLException, IllegalArgumentException { - setRepositoryConfigurationForRepository(repo, isCiEnabled, - verifyBranchRegex, verifyBuildCommand, false, - "N/A", publishBranchRegex, publishBuildCommand, false, "N/A", prebuildCommand, null, rebuildOnUpdate, - false, "N/A", rebuildOnUpdate, null, null, new EmailSettings(), false, false); - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#setRepositoryConfigurationForRepositoryFromRequest(com.atlassian.stash.repository.Repository, javax.servlet.http.HttpServletRequest) - */ - @Override - public void setRepositoryConfigurationForRepositoryFromRequest(Repository repo, HttpServletRequest req) - throws SQLException, NumberFormatException { - - Boolean ciEnabled = getBoolean(req, "ciEnabled"); - String publishBranchRegex = req.getParameter("publishBranchRegex"); - String publishBuildCommand = req.getParameter("publishBuildCommand"); - Boolean isPublishPinned = getBoolean(req, "isPublishPinned"); - String publishLabel = req.getParameter("publishLabel"); - String verifyBranchRegex = req.getParameter("verifyBranchRegex"); - String verifyBuildCommand = req.getParameter("verifyBuildCommand"); - Boolean isVerifyPinned = getBoolean(req, "isVerifyPinned"); - String verifyLabel = req.getParameter("verifyLabel"); - String prebuildCommand = req.getParameter("prebuildCommand"); - String jenkinsServerName = req.getParameter("jenkinsServerName"); - String maxVerifyChainStr = req.getParameter("maxVerifyChain"); - Integer maxVerifyChain = null; - if (maxVerifyChainStr != null && !maxVerifyChainStr.isEmpty()) { - maxVerifyChain = Integer.parseInt(maxVerifyChainStr); - } - Boolean strictVerifyMode = getBoolean(req, "isStrictVerifyMode"); - Boolean preserveJenkinsJobConfig = getBoolean(req, "isPreserveJenkinsJobConfig"); - - Boolean junitEnabled = getBoolean(req, "isJunit"); - String junitPath = req.getParameter("junitPath"); - - Boolean artifactsEnabled = getBoolean(req, "artifactsEnabled"); - String artifactsPath = req.getParameter("artifactsPath"); - - Boolean rebuildOnUpdate = getBoolean(req, "rebuildOnUpdate"); - - EmailSettings emailSettings = getEmailSettings(req); - - setRepositoryConfigurationForRepository(repo, ciEnabled, verifyBranchRegex, verifyBuildCommand, isVerifyPinned, - verifyLabel, publishBranchRegex, publishBuildCommand, isPublishPinned, publishLabel, prebuildCommand, - jenkinsServerName, rebuildOnUpdate, junitEnabled, junitPath, artifactsEnabled, artifactsPath, - maxVerifyChain, emailSettings, strictVerifyMode, preserveJenkinsJobConfig); - RepositoryConfiguration rc = getRepositoryConfigurationForRepository(repo); - setJobTypeStatusMapping(rc, JobType.VERIFY_COMMIT, getBoolean(req, "verificationEnabled")); - setJobTypeStatusMapping(rc, JobType.VERIFY_PR, getBoolean(req, "verifyPREnabled")); - setJobTypeStatusMapping(rc, JobType.PUBLISH, getBoolean(req, "publishEnabled")); - } - - @Override - public void setJobTypeStatusMapping(RepositoryConfiguration rc, JobType jt, Boolean isEnabled) { - JobTypeStatusMapping[] mappings = - ao.find(JobTypeStatusMapping.class, "REPO_CONFIG_ID = ? and JOB_TYPE_RAW = ?", rc.getID(), jt.name()); - if (mappings.length == 0) { - ao.create(JobTypeStatusMapping.class, - new DBParam("REPO_CONFIG_ID", rc.getID()), - new DBParam("JOB_TYPE_RAW", jt.name()), - new DBParam("IS_ENABLED", isEnabled)).save(); - return; - } - mappings[0].setIsEnabled(isEnabled); - mappings[0].save(); - } - - @Override - public Boolean getJobTypeStatusMapping(RepositoryConfiguration rc, JobType jt) { - JobTypeStatusMapping[] mappings = - ao.find(JobTypeStatusMapping.class, "REPO_CONFIG_ID = ? and JOB_TYPE_RAW = ?", rc.getID(), jt.name()); - if (mappings.length == 0) { - return false; - } - return mappings[0].getIsEnabled(); - } - - private EmailSettings getEmailSettings(HttpServletRequest req) { - Boolean emailNotificationsEnabled = getBoolean(req, "isEmailNotificationsEnabled"); - String emailRecipients = req.getParameter("emailRecipients"); - Boolean emailDontNotifyEveryUnstableBuild = getBoolean(req, "isEmailForEveryUnstableBuild"); - Boolean emailSendToIndividuals = getBoolean(req, "isEmailSendToIndividuals"); - Boolean emailPerModuleEmail = getBoolean(req, "isEmailPerModuleEmail"); - return new EmailSettings(emailNotificationsEnabled, emailRecipients, emailDontNotifyEveryUnstableBuild, - emailSendToIndividuals, emailPerModuleEmail); - } - - private boolean getBoolean(HttpServletRequest req, String parameter) { - return (req.getParameter(parameter) == null) ? false : true; - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#setRepositoryConfigurationForRepository(com.atlassian.stash.repository.Repository, boolean, java.lang.String, java.lang.String, boolean, java.lang.String, java.lang.String, java.lang.String, boolean, java.lang.String, java.lang.String, java.lang.String, boolean, boolean, java.lang.String, boolean, java.lang.String, java.lang.Integer, com.palantir.stash.stashbot.config.EmailSettings, boolean, java.lang.Boolean) - */ - @Override - public void - setRepositoryConfigurationForRepository(Repository repo, - boolean isCiEnabled, String verifyBranchRegex, - String verifyBuildCommand, boolean isVerifyPinned, - String verifyLabel, String publishBranchRegex, - String publishBuildCommand, boolean isPublishPinned, String publishLabel, String prebuildCommand, - String jenkinsServerName, boolean rebuildOnUpdate, boolean isJunitEnabled, String junitPath, - boolean artifactsEnabled, String artifactsPath, Integer maxVerifyChain, EmailSettings emailSettings, - boolean strictVerifyMode, Boolean preserveJenkinsJobConfig) - throws SQLException, IllegalArgumentException { - if (jenkinsServerName == null) { - jenkinsServerName = DEFAULT_JENKINS_SERVER_CONFIG_KEY; - } - validateNameExists(jenkinsServerName); - RepositoryConfiguration[] repos = ao.find( - RepositoryConfiguration.class, - Query.select().where("REPO_ID = ?", repo.getId())); - if (repos.length == 0) { - log.info("Creating repository configuration for id: " - + repo.getId().toString()); - RepositoryConfiguration rc = ao.create( - RepositoryConfiguration.class, - new DBParam("REPO_ID", repo.getId()), new DBParam( - "CI_ENABLED", isCiEnabled), new DBParam( - "VERIFY_BRANCH_REGEX", verifyBranchRegex), - new DBParam("VERIFY_BUILD_COMMAND", verifyBuildCommand), - new DBParam("VERIFY_PINNED", isVerifyPinned), - new DBParam("VERIFY_LABEL", verifyLabel), - new DBParam("PUBLISH_BRANCH_REGEX", publishBranchRegex), - new DBParam("PUBLISH_BUILD_COMMAND", publishBuildCommand), - new DBParam("PUBLISH_PINNED", isPublishPinned), - new DBParam("PUBLISH_LABEL", publishLabel), - new DBParam("PREBUILD_COMMAND", prebuildCommand), - new DBParam("JENKINS_SERVER_NAME", jenkinsServerName), - new DBParam("JUNIT_ENABLED", isJunitEnabled), - new DBParam("JUNIT_PATH", junitPath), - new DBParam("ARTIFACTS_ENABLED", artifactsEnabled), - new DBParam("ARTIFACTS_PATH", artifactsPath), - new DBParam("REBUILD_ON_TARGET_UPDATE", rebuildOnUpdate), - new DBParam("EMAIL_NOTIFICATIONS_ENABLED", emailSettings.getEmailNotificationsEnabled()), - new DBParam("EMAIL_FOR_EVERY_UNSTABLE_BUILD", emailSettings.getEmailForEveryUnstableBuild()), - new DBParam("EMAIL_PER_MODULE_EMAIL", emailSettings.getEmailPerModuleEmail()), - new DBParam("EMAIL_RECIPIENTS", emailSettings.getEmailRecipients()), - new DBParam("EMAIL_SEND_TO_INDIVIDUALS", emailSettings.getEmailSendToIndividuals()), - new DBParam("STRICT_VERIFY_MODE", strictVerifyMode), - new DBParam("PRESERVE_JENKINS_JOB_CONFIG", preserveJenkinsJobConfig) - ); - if (maxVerifyChain != null) { - rc.setMaxVerifyChain(maxVerifyChain); - } - rc.save(); - // default the 3 base job types to enabled - setJobTypeStatusMapping(rc, JobType.VERIFY_COMMIT, true); - setJobTypeStatusMapping(rc, JobType.VERIFY_PR, true); - setJobTypeStatusMapping(rc, JobType.PUBLISH, true); - return; - } - RepositoryConfiguration foundRepo = repos[0]; - foundRepo.setCiEnabled(isCiEnabled); - foundRepo.setVerifyBranchRegex(verifyBranchRegex); - foundRepo.setVerifyBuildCommand(verifyBuildCommand); - foundRepo.setVerifyPinned(isVerifyPinned); - foundRepo.setVerifyLabel(verifyLabel); - foundRepo.setPublishBranchRegex(publishBranchRegex); - foundRepo.setPublishBuildCommand(publishBuildCommand); - foundRepo.setPublishPinned(isPublishPinned); - foundRepo.setPublishLabel(publishLabel); - foundRepo.setPrebuildCommand(prebuildCommand); - foundRepo.setJenkinsServerName(jenkinsServerName); - foundRepo.setJunitEnabled(isJunitEnabled); - foundRepo.setJunitPath(junitPath); - foundRepo.setArtifactsEnabled(artifactsEnabled); - foundRepo.setArtifactsPath(artifactsPath); - foundRepo.setRebuildOnTargetUpdate(rebuildOnUpdate); - if (maxVerifyChain != null) { - foundRepo.setMaxVerifyChain(maxVerifyChain); - } - foundRepo.setEmailNotificationsEnabled(emailSettings.getEmailNotificationsEnabled()); - foundRepo.setEmailForEveryUnstableBuild(emailSettings.getEmailForEveryUnstableBuild()); - foundRepo.setEmailPerModuleEmail(emailSettings.getEmailPerModuleEmail()); - foundRepo.setEmailRecipients(emailSettings.getEmailRecipients()); - foundRepo.setEmailSendToIndividuals(emailSettings.getEmailSendToIndividuals()); - foundRepo.setStrictVerifyMode(strictVerifyMode); - foundRepo.setPreserveJenkinsJobConfig(preserveJenkinsJobConfig); - foundRepo.save(); - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#getAllJenkinsServerConfigurations() - */ - @Override - public ImmutableCollection getAllJenkinsServerConfigurations() - throws SQLException { - JenkinsServerConfiguration[] allConfigs = ao - .find(JenkinsServerConfiguration.class); - if (allConfigs.length == 0) { - return ImmutableList.of(getJenkinsServerConfiguration(null)); - } - return ImmutableList.copyOf(allConfigs); - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#getAllJenkinsServerNames() - */ - @Override - public ImmutableCollection getAllJenkinsServerNames() - throws SQLException { - List names = new ArrayList(); - - JenkinsServerConfiguration[] allConfigs = ao - .find(JenkinsServerConfiguration.class); - for (JenkinsServerConfiguration jsc : allConfigs) { - names.add(jsc.getName()); - } - if (allConfigs.length == 0) { - return ImmutableList.of(getJenkinsServerConfiguration(null) - .getName()); - } - return ImmutableList.copyOf(names); - - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#validateName(java.lang.String) - */ - @Override - public void validateName(String name) throws IllegalArgumentException { - if (!name.matches("[a-zA-Z0-9]+")) { - throw new IllegalArgumentException("Name must match [a-zA-Z0-9]+"); - } - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#validateNameExists(java.lang.String) - */ - @Override - public void validateNameExists(String name) throws IllegalArgumentException { - if (name.equals(DEFAULT_JENKINS_SERVER_CONFIG_KEY)) { - return; - } - JenkinsServerConfiguration[] allConfigs = ao - .find(JenkinsServerConfiguration.class); - for (JenkinsServerConfiguration jsc : allConfigs) { - if (jsc.getName().equals(name)) { - return; - } - } - throw new IllegalArgumentException("Jenkins Server name " + name - + " does not exist"); - } - - private String pullRequestToString(PullRequest pr) { - return "[id:" + Long.toString(pr.getId()) + ", from:" - + pr.getFromRef().getLatestChangeset() + ", to:" - + pr.getToRef().getLatestChangeset() + "]"; - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#getPullRequestMetadata(com.atlassian.stash.pull.PullRequest) - */ - @Override - public PullRequestMetadata getPullRequestMetadata(PullRequest pr) { - return getPullRequestMetadata(pr.getToRef().getRepository().getId(), pr.getId(), - pr.getFromRef().getLatestChangeset().toString(), - pr.getToRef().getLatestChangeset().toString()); - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#getPullRequestMetadata(int, java.lang.Long, java.lang.String, java.lang.String) - */ - @Override - public PullRequestMetadata getPullRequestMetadata(int repoId, Long prId, String fromSha, String toSha) { - // We have to check repoId being equal to -1 so that this works with old data. - PullRequestMetadata[] prms = ao.find(PullRequestMetadata.class, - "(REPO_ID = ? OR REPO_ID = -1) AND PULL_REQUEST_ID = ? and TO_SHA = ? and FROM_SHA = ?", repoId, prId, - toSha, fromSha); - if (prms.length == 0) { - // new/updated PR, create a new object - log.info("Creating PR Metadata for pull request: repo id:" + repoId - + "pr id: " + prId + ", fromSha: " + fromSha + ", toSha: " + toSha); - PullRequestMetadata prm = - ao.create( - PullRequestMetadata.class, - new DBParam("REPO_ID", repoId), - new DBParam("PULL_REQUEST_ID", prId), - new DBParam("TO_SHA", toSha), - new DBParam("FROM_SHA", fromSha)); - prm.save(); - return prm; - - } - return prms[0]; - } - - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#getPullRequestMetadataWithoutToRef(com.atlassian.stash.pull.PullRequest) - */ - @Override - public ImmutableList getPullRequestMetadataWithoutToRef(PullRequest pr) { - Long id = pr.getId(); - String fromSha = pr.getFromRef().getLatestChangeset().toString(); - String toSha = pr.getToRef().getLatestChangeset().toString(); - - PullRequestMetadata[] prms = ao.find(PullRequestMetadata.class, - "PULL_REQUEST_ID = ? and FROM_SHA = ?", id, fromSha); - if (prms.length == 0) { - // new/updated PR, create a new object - log.info("Creating PR Metadata for pull request: " - + pullRequestToString(pr)); - PullRequestMetadata prm = - ao.create( - PullRequestMetadata.class, - new DBParam("PULL_REQUEST_ID", id), - new DBParam("TO_SHA", toSha), - new DBParam("FROM_SHA", fromSha)); - prm.save(); - return ImmutableList.of(prm); - - } - return ImmutableList.copyOf(prms); - } - - // Automatically sets the fromHash and toHash from the PullRequest object - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#setPullRequestMetadata(com.atlassian.stash.pull.PullRequest, java.lang.Boolean, java.lang.Boolean, java.lang.Boolean) - */ - @Override - public void setPullRequestMetadata(PullRequest pr, Boolean buildStarted, - Boolean success, Boolean override) { - setPullRequestMetadata(pr, pr.getFromRef().getLatestChangeset(), - pr.getToRef().getLatestChangeset(), buildStarted, success, override); - } - - // Allows fromHash and toHash to be set by the caller, in case we are referring to older commits - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#setPullRequestMetadata(com.atlassian.stash.pull.PullRequest, java.lang.String, java.lang.String, java.lang.Boolean, java.lang.Boolean, java.lang.Boolean) - */ - @Override - public void setPullRequestMetadata(PullRequest pr, String fromHash, String toHash, Boolean buildStarted, - Boolean success, Boolean override) { - setPullRequestMetadata(pr, fromHash, toHash, buildStarted, success, override, null); - } - - // Allows fromHash and toHash to be set by the caller, in case we are referring to older commits - /* (non-Javadoc) - * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService#setPullRequestMetadata(com.atlassian.stash.pull.PullRequest, java.lang.String, java.lang.String, java.lang.Boolean, java.lang.Boolean, java.lang.Boolean, java.lang.Boolean) - */ - @Override - public void setPullRequestMetadata(PullRequest pr, String fromHash, String toHash, Boolean buildStarted, - Boolean success, Boolean override, Boolean failed) { - PullRequestMetadata prm = - getPullRequestMetadata(pr.getToRef().getRepository().getId(), pr.getId(), fromHash, toHash); - if (buildStarted != null) { - prm.setBuildStarted(buildStarted); - } - if (success != null) { - prm.setSuccess(success); - } - if (override != null) { - prm.setOverride(override); - } - if (failed != null) { - prm.setFailed(failed); - } - - prm.save(); - publisher.publish(new StashbotMetadataUpdatedEvent(this, pr)); - } +public class ConfigurationPersistenceImpl implements +ConfigurationPersistenceService { + + private final ActiveObjects ao; + private final Logger log; + private final EventPublisher publisher; + + private static final String DEFAULT_JENKINS_SERVER_CONFIG_KEY = "default"; + + public ConfigurationPersistenceImpl(ActiveObjects ao, + PluginLoggerFactory lf, EventPublisher publisher) { + this.ao = ao; + this.log = lf.getLoggerForThis(this); + this.publisher = publisher; + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * deleteJenkinsServerConfiguration(java.lang.String) + */ + @Override + public void deleteJenkinsServerConfiguration(String name) { + JenkinsServerConfiguration[] configs = ao.find( + JenkinsServerConfiguration.class, + Query.select().where("NAME = ?", name)); + if (configs.length == 0) { + return; + } + for (JenkinsServerConfiguration jsc : configs) { + ao.delete(jsc); + } + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * getJenkinsServerConfiguration(java.lang.String) + */ + @Override + public JenkinsServerConfiguration getJenkinsServerConfiguration(String name) + throws SQLException { + if (name == null) { + name = DEFAULT_JENKINS_SERVER_CONFIG_KEY; + } + JenkinsServerConfiguration[] configs = ao.find( + JenkinsServerConfiguration.class, + Query.select().where("NAME = ?", name)); + if (configs.length == 0) { + // just use the defaults + return ao.create(JenkinsServerConfiguration.class, new DBParam( + "NAME", name)); + } + + String url = configs[0].getUrl(); + if (url.endsWith("/")) { + url = url.substring(0, url.length() - 1); + configs[0].setUrl(url); + configs[0].save(); + } + String template = configs[0].getPrefixTemplate(); + if (template.endsWith("/")) { + template = template.substring(0, template.length() - 1); + configs[0].setPrefixTemplate(template); + configs[0].save(); + } + return configs[0]; + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * setJenkinsServerConfigurationFromRequest + * (javax.servlet.http.HttpServletRequest) + */ + @Override + public void setJenkinsServerConfigurationFromRequest(HttpServletRequest req) + throws SQLException, NumberFormatException { + + String name = req.getParameter("name"); + String url = req.getParameter("url"); + String username = req.getParameter("username"); + String password = req.getParameter("password"); + AuthenticationMode am = AuthenticationMode.fromMode(req + .getParameter("authenticationMode")); + String stashUsername = req.getParameter("stashUsername"); + String stashPassword = req.getParameter("stashPassword"); + Integer maxVerifyChain = Integer.parseInt(req + .getParameter("maxVerifyChain")); + String prefixTemplate = req.getParameter("prefixTemplate"); + String lockStr = req.getParameter("locked"); + Boolean isLocked = (lockStr == null || !lockStr.equals("on")) ? false + : true; + + setJenkinsServerConfiguration(name, url, username, password, am, + stashUsername, stashPassword, maxVerifyChain, prefixTemplate, + isLocked); + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * setJenkinsServerConfiguration(java.lang.String, java.lang.String, + * java.lang.String, java.lang.String, java.lang.String, java.lang.String, + * java.lang.Integer) + */ + @Override + @Deprecated + public void setJenkinsServerConfiguration(String name, String url, + String username, String password, String stashUsername, + String stashPassword, Integer maxVerifyChain) throws SQLException { + setJenkinsServerConfiguration(name, url, username, password, + AuthenticationMode.USERNAME_AND_PASSWORD, stashUsername, + stashPassword, maxVerifyChain, false); + } + + @Override + public void setJenkinsServerConfiguration(String name, String url, + String username, String password, + AuthenticationMode authenticationMode, String stashUsername, + String stashPassword, Integer maxVerifyChain, Boolean isLocked) + throws SQLException { + setJenkinsServerConfiguration(name, url, username, password, + authenticationMode, stashUsername, stashPassword, + maxVerifyChain, "", false); + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * setJenkinsServerConfiguration(java.lang.String, java.lang.String, + * java.lang.String, java.lang.String, + * com.palantir.stash.stashbot.config.JenkinsServerConfiguration + * .AuthenticationMode, java.lang.String, java.lang.String, + * java.lang.Integer, java.lang.Boolean) + */ + @Override + public void setJenkinsServerConfiguration(String name, String url, + String username, String password, + AuthenticationMode authenticationMode, String stashUsername, + String stashPassword, Integer maxVerifyChain, + String prefixTemplate, Boolean isLocked) throws SQLException { + if (name == null) { + name = DEFAULT_JENKINS_SERVER_CONFIG_KEY; + } + validateName(name); + JenkinsServerConfiguration[] configs = ao.find( + JenkinsServerConfiguration.class, + Query.select().where("NAME = ?", name)); + + if (configs.length == 0) { + log.info("Creating jenkins configuration: " + name); + ao.create(JenkinsServerConfiguration.class, new DBParam("NAME", + name), new DBParam("URL", url), new DBParam("USERNAME", + username), new DBParam("PASSWORD", password), new DBParam( + "STASH_USERNAME", stashUsername), new DBParam( + "STASH_PASSWORD", stashPassword), new DBParam( + "MAX_VERIFY_CHAIN", maxVerifyChain), new DBParam( + "PREFIX_TEMPLATE", prefixTemplate), new DBParam("LOCKED", + isLocked)); + return; + } + // already exists, so update it + configs[0].setName(name); + configs[0].setUrl(url); + configs[0].setUsername(username); + configs[0].setPassword(password); + configs[0].setAuthenticationMode(authenticationMode); + configs[0].setStashUsername(stashUsername); + configs[0].setStashPassword(stashPassword); + configs[0].setMaxVerifyChain(maxVerifyChain); + configs[0].setPrefixTemplate(prefixTemplate); + configs[0].setLocked(isLocked); + configs[0].save(); + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * getRepositoryConfigurationForRepository + * (com.atlassian.stash.repository.Repository) + */ + @Override + public RepositoryConfiguration getRepositoryConfigurationForRepository( + Repository repo) throws SQLException { + RepositoryConfiguration[] repos = ao.find( + RepositoryConfiguration.class, + Query.select().where("REPO_ID = ?", repo.getId())); + if (repos.length == 0) { + // just use the defaults + RepositoryConfiguration rc = ao.create( + RepositoryConfiguration.class, + new DBParam("REPO_ID", repo.getId())); + rc.save(); + // default the 3 base job types to enabled + setJobTypeStatusMapping(rc, JobType.VERIFY_COMMIT, true); + setJobTypeStatusMapping(rc, JobType.VERIFY_PR, true); + setJobTypeStatusMapping(rc, JobType.PUBLISH, true); + return rc; + } + return repos[0]; + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * setRepositoryConfigurationForRepository + * (com.atlassian.stash.repository.Repository, boolean, java.lang.String, + * java.lang.String, java.lang.String, java.lang.String, java.lang.String, + * boolean) + */ + @Override + public void setRepositoryConfigurationForRepository(Repository repo, + boolean isCiEnabled, String verifyBranchRegex, + String verifyBuildCommand, String publishBranchRegex, + String publishBuildCommand, String prebuildCommand, + boolean rebuildOnUpdate) throws SQLException, + IllegalArgumentException { + setRepositoryConfigurationForRepository(repo, isCiEnabled, + verifyBranchRegex, verifyBuildCommand, false, "N/A", + publishBranchRegex, publishBuildCommand, false, "N/A", + prebuildCommand, null, rebuildOnUpdate, false, "N/A", + rebuildOnUpdate, null, null, new EmailSettings(), false, false); + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * setRepositoryConfigurationForRepositoryFromRequest + * (com.atlassian.stash.repository.Repository, + * javax.servlet.http.HttpServletRequest) + */ + @Override + public void setRepositoryConfigurationForRepositoryFromRequest( + Repository repo, HttpServletRequest req) throws SQLException, + NumberFormatException { + + Boolean ciEnabled = getBoolean(req, "ciEnabled"); + String publishBranchRegex = req.getParameter("publishBranchRegex"); + String publishBuildCommand = req.getParameter("publishBuildCommand"); + Boolean isPublishPinned = getBoolean(req, "isPublishPinned"); + String publishLabel = req.getParameter("publishLabel"); + String verifyBranchRegex = req.getParameter("verifyBranchRegex"); + String verifyBuildCommand = req.getParameter("verifyBuildCommand"); + Boolean isVerifyPinned = getBoolean(req, "isVerifyPinned"); + String verifyLabel = req.getParameter("verifyLabel"); + String prebuildCommand = req.getParameter("prebuildCommand"); + String jenkinsServerName = req.getParameter("jenkinsServerName"); + String maxVerifyChainStr = req.getParameter("maxVerifyChain"); + Integer maxVerifyChain = null; + if (maxVerifyChainStr != null && !maxVerifyChainStr.isEmpty()) { + maxVerifyChain = Integer.parseInt(maxVerifyChainStr); + } + Boolean strictVerifyMode = getBoolean(req, "isStrictVerifyMode"); + Boolean preserveJenkinsJobConfig = getBoolean(req, + "isPreserveJenkinsJobConfig"); + + Boolean junitEnabled = getBoolean(req, "isJunit"); + String junitPath = req.getParameter("junitPath"); + + Boolean artifactsEnabled = getBoolean(req, "artifactsEnabled"); + String artifactsPath = req.getParameter("artifactsPath"); + + Boolean rebuildOnUpdate = getBoolean(req, "rebuildOnUpdate"); + + EmailSettings emailSettings = getEmailSettings(req); + + setRepositoryConfigurationForRepository(repo, ciEnabled, + verifyBranchRegex, verifyBuildCommand, isVerifyPinned, + verifyLabel, publishBranchRegex, publishBuildCommand, + isPublishPinned, publishLabel, prebuildCommand, + jenkinsServerName, rebuildOnUpdate, junitEnabled, junitPath, + artifactsEnabled, artifactsPath, maxVerifyChain, emailSettings, + strictVerifyMode, preserveJenkinsJobConfig); + RepositoryConfiguration rc = getRepositoryConfigurationForRepository(repo); + setJobTypeStatusMapping(rc, JobType.VERIFY_COMMIT, + getBoolean(req, "verificationEnabled")); + setJobTypeStatusMapping(rc, JobType.VERIFY_PR, + getBoolean(req, "verifyPREnabled")); + setJobTypeStatusMapping(rc, JobType.PUBLISH, + getBoolean(req, "publishEnabled")); + } + + @Override + public void setJobTypeStatusMapping(RepositoryConfiguration rc, JobType jt, + Boolean isEnabled) { + JobTypeStatusMapping[] mappings = ao.find(JobTypeStatusMapping.class, + "REPO_CONFIG_ID = ? and JOB_TYPE_RAW = ?", rc.getID(), + jt.name()); + if (mappings.length == 0) { + ao.create(JobTypeStatusMapping.class, + new DBParam("REPO_CONFIG_ID", rc.getID()), + new DBParam("JOB_TYPE_RAW", jt.name()), + new DBParam("IS_ENABLED", isEnabled)).save(); + return; + } + mappings[0].setIsEnabled(isEnabled); + mappings[0].save(); + } + + @Override + public Boolean getJobTypeStatusMapping(RepositoryConfiguration rc, + JobType jt) { + JobTypeStatusMapping[] mappings = ao.find(JobTypeStatusMapping.class, + "REPO_CONFIG_ID = ? and JOB_TYPE_RAW = ?", rc.getID(), + jt.name()); + if (mappings.length == 0) { + return false; + } + return mappings[0].getIsEnabled(); + } + + private EmailSettings getEmailSettings(HttpServletRequest req) { + Boolean emailNotificationsEnabled = getBoolean(req, + "isEmailNotificationsEnabled"); + String emailRecipients = req.getParameter("emailRecipients"); + Boolean emailDontNotifyEveryUnstableBuild = getBoolean(req, + "isEmailForEveryUnstableBuild"); + Boolean emailSendToIndividuals = getBoolean(req, + "isEmailSendToIndividuals"); + Boolean emailPerModuleEmail = getBoolean(req, "isEmailPerModuleEmail"); + return new EmailSettings(emailNotificationsEnabled, emailRecipients, + emailDontNotifyEveryUnstableBuild, emailSendToIndividuals, + emailPerModuleEmail); + } + + private boolean getBoolean(HttpServletRequest req, String parameter) { + return (req.getParameter(parameter) == null) ? false : true; + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * setRepositoryConfigurationForRepository + * (com.atlassian.stash.repository.Repository, boolean, java.lang.String, + * java.lang.String, boolean, java.lang.String, java.lang.String, + * java.lang.String, boolean, java.lang.String, java.lang.String, + * java.lang.String, boolean, boolean, java.lang.String, boolean, + * java.lang.String, java.lang.Integer, + * com.palantir.stash.stashbot.config.EmailSettings, boolean, + * java.lang.Boolean) + */ + @Override + public void setRepositoryConfigurationForRepository(Repository repo, + boolean isCiEnabled, String verifyBranchRegex, + String verifyBuildCommand, boolean isVerifyPinned, + String verifyLabel, String publishBranchRegex, + String publishBuildCommand, boolean isPublishPinned, + String publishLabel, String prebuildCommand, + String jenkinsServerName, boolean rebuildOnUpdate, + boolean isJunitEnabled, String junitPath, boolean artifactsEnabled, + String artifactsPath, Integer maxVerifyChain, + EmailSettings emailSettings, boolean strictVerifyMode, + Boolean preserveJenkinsJobConfig) throws SQLException, + IllegalArgumentException { + if (jenkinsServerName == null) { + jenkinsServerName = DEFAULT_JENKINS_SERVER_CONFIG_KEY; + } + validateNameExists(jenkinsServerName); + RepositoryConfiguration[] repos = ao.find( + RepositoryConfiguration.class, + Query.select().where("REPO_ID = ?", repo.getId())); + if (repos.length == 0) { + log.info("Creating repository configuration for id: " + + repo.getId().toString()); + RepositoryConfiguration rc = ao.create( + RepositoryConfiguration.class, + new DBParam("REPO_ID", repo.getId()), + new DBParam("CI_ENABLED", isCiEnabled), + new DBParam("VERIFY_BRANCH_REGEX", verifyBranchRegex), + new DBParam("VERIFY_BUILD_COMMAND", verifyBuildCommand), + new DBParam("VERIFY_PINNED", isVerifyPinned), + new DBParam("VERIFY_LABEL", verifyLabel), + new DBParam("PUBLISH_BRANCH_REGEX", publishBranchRegex), + new DBParam("PUBLISH_BUILD_COMMAND", publishBuildCommand), + new DBParam("PUBLISH_PINNED", isPublishPinned), + new DBParam("PUBLISH_LABEL", publishLabel), + new DBParam("PREBUILD_COMMAND", prebuildCommand), + new DBParam("JENKINS_SERVER_NAME", jenkinsServerName), + new DBParam("JUNIT_ENABLED", isJunitEnabled), + new DBParam("JUNIT_PATH", junitPath), + new DBParam("ARTIFACTS_ENABLED", artifactsEnabled), + new DBParam("ARTIFACTS_PATH", artifactsPath), + new DBParam("REBUILD_ON_TARGET_UPDATE", rebuildOnUpdate), + new DBParam("EMAIL_NOTIFICATIONS_ENABLED", emailSettings + .getEmailNotificationsEnabled()), + new DBParam("EMAIL_FOR_EVERY_UNSTABLE_BUILD", emailSettings + .getEmailForEveryUnstableBuild()), + new DBParam("EMAIL_PER_MODULE_EMAIL", emailSettings + .getEmailPerModuleEmail()), + new DBParam("EMAIL_RECIPIENTS", emailSettings + .getEmailRecipients()), + new DBParam("EMAIL_SEND_TO_INDIVIDUALS", emailSettings + .getEmailSendToIndividuals()), new DBParam( + "STRICT_VERIFY_MODE", strictVerifyMode), + new DBParam("PRESERVE_JENKINS_JOB_CONFIG", + preserveJenkinsJobConfig)); + if (maxVerifyChain != null) { + rc.setMaxVerifyChain(maxVerifyChain); + } + rc.save(); + // default the 3 base job types to enabled + setJobTypeStatusMapping(rc, JobType.VERIFY_COMMIT, true); + setJobTypeStatusMapping(rc, JobType.VERIFY_PR, true); + setJobTypeStatusMapping(rc, JobType.PUBLISH, true); + return; + } + RepositoryConfiguration foundRepo = repos[0]; + foundRepo.setCiEnabled(isCiEnabled); + foundRepo.setVerifyBranchRegex(verifyBranchRegex); + foundRepo.setVerifyBuildCommand(verifyBuildCommand); + foundRepo.setVerifyPinned(isVerifyPinned); + foundRepo.setVerifyLabel(verifyLabel); + foundRepo.setPublishBranchRegex(publishBranchRegex); + foundRepo.setPublishBuildCommand(publishBuildCommand); + foundRepo.setPublishPinned(isPublishPinned); + foundRepo.setPublishLabel(publishLabel); + foundRepo.setPrebuildCommand(prebuildCommand); + foundRepo.setJenkinsServerName(jenkinsServerName); + foundRepo.setJunitEnabled(isJunitEnabled); + foundRepo.setJunitPath(junitPath); + foundRepo.setArtifactsEnabled(artifactsEnabled); + foundRepo.setArtifactsPath(artifactsPath); + foundRepo.setRebuildOnTargetUpdate(rebuildOnUpdate); + if (maxVerifyChain != null) { + foundRepo.setMaxVerifyChain(maxVerifyChain); + } + foundRepo.setEmailNotificationsEnabled(emailSettings + .getEmailNotificationsEnabled()); + foundRepo.setEmailForEveryUnstableBuild(emailSettings + .getEmailForEveryUnstableBuild()); + foundRepo + .setEmailPerModuleEmail(emailSettings.getEmailPerModuleEmail()); + foundRepo.setEmailRecipients(emailSettings.getEmailRecipients()); + foundRepo.setEmailSendToIndividuals(emailSettings + .getEmailSendToIndividuals()); + foundRepo.setStrictVerifyMode(strictVerifyMode); + foundRepo.setPreserveJenkinsJobConfig(preserveJenkinsJobConfig); + foundRepo.save(); + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * getAllJenkinsServerConfigurations() + */ + @Override + public ImmutableCollection getAllJenkinsServerConfigurations() + throws SQLException { + JenkinsServerConfiguration[] allConfigs = ao + .find(JenkinsServerConfiguration.class); + if (allConfigs.length == 0) { + return ImmutableList.of(getJenkinsServerConfiguration(null)); + } + return ImmutableList.copyOf(allConfigs); + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * getAllJenkinsServerNames() + */ + @Override + public ImmutableCollection getAllJenkinsServerNames() + throws SQLException { + List names = new ArrayList(); + + JenkinsServerConfiguration[] allConfigs = ao + .find(JenkinsServerConfiguration.class); + for (JenkinsServerConfiguration jsc : allConfigs) { + names.add(jsc.getName()); + } + if (allConfigs.length == 0) { + return ImmutableList.of(getJenkinsServerConfiguration(null) + .getName()); + } + return ImmutableList.copyOf(names); + + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * validateName(java.lang.String) + */ + @Override + public void validateName(String name) throws IllegalArgumentException { + if (!name.matches("[a-zA-Z0-9]+")) { + throw new IllegalArgumentException("Name must match [a-zA-Z0-9]+"); + } + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * validateNameExists(java.lang.String) + */ + @Override + public void validateNameExists(String name) throws IllegalArgumentException { + if (name.equals(DEFAULT_JENKINS_SERVER_CONFIG_KEY)) { + return; + } + JenkinsServerConfiguration[] allConfigs = ao + .find(JenkinsServerConfiguration.class); + for (JenkinsServerConfiguration jsc : allConfigs) { + if (jsc.getName().equals(name)) { + return; + } + } + throw new IllegalArgumentException("Jenkins Server name " + name + + " does not exist"); + } + + private String pullRequestToString(PullRequest pr) { + return "[id:" + Long.toString(pr.getId()) + ", from:" + + pr.getFromRef().getLatestChangeset() + ", to:" + + pr.getToRef().getLatestChangeset() + "]"; + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * getPullRequestMetadata(com.atlassian.stash.pull.PullRequest) + */ + @Override + public PullRequestMetadata getPullRequestMetadata(PullRequest pr) { + return getPullRequestMetadata(pr.getToRef().getRepository().getId(), + pr.getId(), pr.getFromRef().getLatestChangeset().toString(), pr + .getToRef().getLatestChangeset().toString()); + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * getPullRequestMetadata(int, java.lang.Long, java.lang.String, + * java.lang.String) + */ + @Override + public PullRequestMetadata getPullRequestMetadata(int repoId, Long prId, + String fromSha, String toSha) { + // We have to check repoId being equal to -1 so that this works with old + // data. + PullRequestMetadata[] prms = ao + .find(PullRequestMetadata.class, + "(REPO_ID = ? OR REPO_ID = -1) AND PULL_REQUEST_ID = ? and TO_SHA = ? and FROM_SHA = ?", + repoId, prId, toSha, fromSha); + if (prms.length == 0) { + // new/updated PR, create a new object + log.info("Creating PR Metadata for pull request: repo id:" + repoId + + "pr id: " + prId + ", fromSha: " + fromSha + ", toSha: " + + toSha); + PullRequestMetadata prm = ao.create(PullRequestMetadata.class, + new DBParam("REPO_ID", repoId), new DBParam( + "PULL_REQUEST_ID", prId), new DBParam("TO_SHA", + toSha), new DBParam("FROM_SHA", fromSha)); + prm.save(); + return prm; + + } + return prms[0]; + } + + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * getPullRequestMetadataWithoutToRef(com.atlassian.stash.pull.PullRequest) + */ + @Override + public ImmutableList getPullRequestMetadataWithoutToRef( + PullRequest pr) { + Long id = pr.getId(); + String fromSha = pr.getFromRef().getLatestChangeset().toString(); + String toSha = pr.getToRef().getLatestChangeset().toString(); + + PullRequestMetadata[] prms = ao.find(PullRequestMetadata.class, + "PULL_REQUEST_ID = ? and FROM_SHA = ?", id, fromSha); + if (prms.length == 0) { + // new/updated PR, create a new object + log.info("Creating PR Metadata for pull request: " + + pullRequestToString(pr)); + PullRequestMetadata prm = ao.create(PullRequestMetadata.class, + new DBParam("PULL_REQUEST_ID", id), new DBParam("TO_SHA", + toSha), new DBParam("FROM_SHA", fromSha)); + prm.save(); + return ImmutableList.of(prm); + + } + return ImmutableList.copyOf(prms); + } + + // Automatically sets the fromHash and toHash from the PullRequest object + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * setPullRequestMetadata(com.atlassian.stash.pull.PullRequest, + * java.lang.Boolean, java.lang.Boolean, java.lang.Boolean) + */ + @Override + public void setPullRequestMetadata(PullRequest pr, Boolean buildStarted, + Boolean success, Boolean override) { + setPullRequestMetadata(pr, pr.getFromRef().getLatestChangeset(), pr + .getToRef().getLatestChangeset(), buildStarted, success, + override); + } + + // Allows fromHash and toHash to be set by the caller, in case we are + // referring to older commits + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * setPullRequestMetadata(com.atlassian.stash.pull.PullRequest, + * java.lang.String, java.lang.String, java.lang.Boolean, java.lang.Boolean, + * java.lang.Boolean) + */ + @Override + public void setPullRequestMetadata(PullRequest pr, String fromHash, + String toHash, Boolean buildStarted, Boolean success, + Boolean override) { + setPullRequestMetadata(pr, fromHash, toHash, buildStarted, success, + override, null); + } + + // Allows fromHash and toHash to be set by the caller, in case we are + // referring to older commits + /* + * (non-Javadoc) + * + * @see com.palantir.stash.stashbot.config.ConfigurationPersistenceService# + * setPullRequestMetadata(com.atlassian.stash.pull.PullRequest, + * java.lang.String, java.lang.String, java.lang.Boolean, java.lang.Boolean, + * java.lang.Boolean, java.lang.Boolean) + */ + @Override + public void setPullRequestMetadata(PullRequest pr, String fromHash, + String toHash, Boolean buildStarted, Boolean success, + Boolean override, Boolean failed) { + PullRequestMetadata prm = getPullRequestMetadata(pr.getToRef() + .getRepository().getId(), pr.getId(), fromHash, toHash); + if (buildStarted != null) { + prm.setBuildStarted(buildStarted); + } + if (success != null) { + prm.setSuccess(success); + } + if (override != null) { + prm.setOverride(override); + } + if (failed != null) { + prm.setFailed(failed); + } + + prm.save(); + publisher.publish(new StashbotMetadataUpdatedEvent(this, pr)); + } } diff --git a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java index ef7325a..65158dd 100644 --- a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java +++ b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java @@ -31,124 +31,147 @@ @Transactional public interface ConfigurationPersistenceService { - public abstract void deleteJenkinsServerConfiguration(String name); - - public abstract JenkinsServerConfiguration getJenkinsServerConfiguration(String name) - throws SQLException; - - public abstract void setJenkinsServerConfigurationFromRequest(HttpServletRequest req) throws SQLException, - NumberFormatException; - - /** - * @deprecated Use - * {@link ConfigurationPersistenceImpl#setJenkinsServerConfiguration(String, String, String, String, AuthenticationMode, String, String, Integer)} - * instead - */ - @Deprecated - public abstract void setJenkinsServerConfiguration(String name, String url, - String username, String password, String stashUsername, String stashPassword, Integer maxVerifyChain) - throws SQLException; - - public abstract void setJenkinsServerConfiguration(String name, String url, - String username, String password, AuthenticationMode authenticationMode, String stashUsername, - String stashPassword, Integer maxVerifyChain, Boolean isLocked) - throws SQLException; - - public abstract RepositoryConfiguration getRepositoryConfigurationForRepository( - Repository repo) throws SQLException; - - public abstract void setRepositoryConfigurationForRepository(Repository repo, - boolean isCiEnabled, String verifyBranchRegex, - String verifyBuildCommand, String publishBranchRegex, - String publishBuildCommand, String prebuildCommand, boolean rebuildOnUpdate) - throws SQLException, IllegalArgumentException; - - public abstract void setRepositoryConfigurationForRepositoryFromRequest(Repository repo, HttpServletRequest req) - throws SQLException, NumberFormatException; - - public abstract void - setRepositoryConfigurationForRepository(Repository repo, - boolean isCiEnabled, String verifyBranchRegex, - String verifyBuildCommand, boolean isVerifyPinned, - String verifyLabel, String publishBranchRegex, - String publishBuildCommand, boolean isPublishPinned, String publishLabel, String prebuildCommand, - String jenkinsServerName, boolean rebuildOnUpdate, boolean isJunitEnabled, String junitPath, - boolean artifactsEnabled, String artifactsPath, Integer maxVerifyChain, EmailSettings emailSettings, - boolean strictVerifyMode, Boolean preserveJenkinsJobConfig) - throws SQLException, IllegalArgumentException; - - public abstract ImmutableCollection getAllJenkinsServerConfigurations() - throws SQLException; - - public abstract ImmutableCollection getAllJenkinsServerNames() - throws SQLException; - - public abstract void validateName(String name) throws IllegalArgumentException; - - public abstract void validateNameExists(String name) throws IllegalArgumentException; - - public abstract PullRequestMetadata getPullRequestMetadata(PullRequest pr); - - public abstract PullRequestMetadata getPullRequestMetadata(int repoId, Long prId, String fromSha, String toSha); - - public abstract ImmutableList getPullRequestMetadataWithoutToRef(PullRequest pr); - - // Automatically sets the fromHash and toHash from the PullRequest object - public abstract void setPullRequestMetadata(PullRequest pr, Boolean buildStarted, - Boolean success, Boolean override); - - // Allows fromHash and toHash to be set by the caller, in case we are referring to older commits - public abstract void setPullRequestMetadata(PullRequest pr, String fromHash, String toHash, Boolean buildStarted, - Boolean success, Boolean override); - - // Allows fromHash and toHash to be set by the caller, in case we are referring to older commits - public abstract void setPullRequestMetadata(PullRequest pr, String fromHash, String toHash, Boolean buildStarted, - Boolean success, Boolean override, Boolean failed); - - public abstract Boolean getJobTypeStatusMapping(RepositoryConfiguration rc, JobType jt); - - public abstract void setJobTypeStatusMapping(RepositoryConfiguration rc, JobType jt, Boolean isEnabled); - - public static class EmailSettings { - - private final Boolean emailNotificationsEnabled; - private final String emailRecipients; - private final Boolean emailForEveryUnstableBuild; - private final Boolean emailSendToIndividuals; - private final Boolean emailPerModuleEmail; - - public EmailSettings() { - this(false, "", false, false, false); - } - - public EmailSettings(Boolean emailNotificationsEnabled, String emailRecipients, - Boolean emailForEveryUnstableBuild, Boolean emailSendToIndividuals, Boolean emailPerModuleEmail) { - this.emailNotificationsEnabled = emailNotificationsEnabled; - this.emailRecipients = emailRecipients; - this.emailForEveryUnstableBuild = emailForEveryUnstableBuild; - this.emailSendToIndividuals = emailSendToIndividuals; - this.emailPerModuleEmail = emailPerModuleEmail; - } - - public Boolean getEmailNotificationsEnabled() { - return emailNotificationsEnabled; - } - - public String getEmailRecipients() { - return emailRecipients; - } - - public Boolean getEmailForEveryUnstableBuild() { - return emailForEveryUnstableBuild; - } - - public Boolean getEmailSendToIndividuals() { - return emailSendToIndividuals; - } - - public Boolean getEmailPerModuleEmail() { - return emailPerModuleEmail; - } - } + public abstract void deleteJenkinsServerConfiguration(String name); + + public abstract JenkinsServerConfiguration getJenkinsServerConfiguration( + String name) throws SQLException; + + public abstract void setJenkinsServerConfigurationFromRequest( + HttpServletRequest req) throws SQLException, NumberFormatException; + + /** + * @deprecated Use + * {@link ConfigurationPersistenceImpl#setJenkinsServerConfiguration(String, String, String, String, AuthenticationMode, String, String, Integer)} + * instead + */ + @Deprecated + public abstract void setJenkinsServerConfiguration(String name, String url, + String username, String password, String stashUsername, + String stashPassword, Integer maxVerifyChain) throws SQLException; + + @Deprecated + public abstract void setJenkinsServerConfiguration(String name, String url, + String username, String password, + AuthenticationMode authenticationMode, String stashUsername, + String stashPassword, Integer maxVerifyChain, Boolean isLocked) + throws SQLException; + + public abstract void setJenkinsServerConfiguration(String name, String url, + String username, String password, + AuthenticationMode authenticationMode, String stashUsername, + String stashPassword, Integer maxVerifyChain, + String prefixTemplate, Boolean isLocked) throws SQLException; + + public abstract RepositoryConfiguration getRepositoryConfigurationForRepository( + Repository repo) throws SQLException; + + public abstract void setRepositoryConfigurationForRepository( + Repository repo, boolean isCiEnabled, String verifyBranchRegex, + String verifyBuildCommand, String publishBranchRegex, + String publishBuildCommand, String prebuildCommand, + boolean rebuildOnUpdate) throws SQLException, + IllegalArgumentException; + + public abstract void setRepositoryConfigurationForRepositoryFromRequest( + Repository repo, HttpServletRequest req) throws SQLException, + NumberFormatException; + + public abstract void setRepositoryConfigurationForRepository( + Repository repo, boolean isCiEnabled, String verifyBranchRegex, + String verifyBuildCommand, boolean isVerifyPinned, + String verifyLabel, String publishBranchRegex, + String publishBuildCommand, boolean isPublishPinned, + String publishLabel, String prebuildCommand, + String jenkinsServerName, boolean rebuildOnUpdate, + boolean isJunitEnabled, String junitPath, boolean artifactsEnabled, + String artifactsPath, Integer maxVerifyChain, + EmailSettings emailSettings, boolean strictVerifyMode, + Boolean preserveJenkinsJobConfig) throws SQLException, + IllegalArgumentException; + + public abstract ImmutableCollection getAllJenkinsServerConfigurations() + throws SQLException; + + public abstract ImmutableCollection getAllJenkinsServerNames() + throws SQLException; + + public abstract void validateName(String name) + throws IllegalArgumentException; + + public abstract void validateNameExists(String name) + throws IllegalArgumentException; + + public abstract PullRequestMetadata getPullRequestMetadata(PullRequest pr); + + public abstract PullRequestMetadata getPullRequestMetadata(int repoId, + Long prId, String fromSha, String toSha); + + public abstract ImmutableList getPullRequestMetadataWithoutToRef( + PullRequest pr); + + // Automatically sets the fromHash and toHash from the PullRequest object + public abstract void setPullRequestMetadata(PullRequest pr, + Boolean buildStarted, Boolean success, Boolean override); + + // Allows fromHash and toHash to be set by the caller, in case we are + // referring to older commits + public abstract void setPullRequestMetadata(PullRequest pr, + String fromHash, String toHash, Boolean buildStarted, + Boolean success, Boolean override); + + // Allows fromHash and toHash to be set by the caller, in case we are + // referring to older commits + public abstract void setPullRequestMetadata(PullRequest pr, + String fromHash, String toHash, Boolean buildStarted, + Boolean success, Boolean override, Boolean failed); + + public abstract Boolean getJobTypeStatusMapping(RepositoryConfiguration rc, + JobType jt); + + public abstract void setJobTypeStatusMapping(RepositoryConfiguration rc, + JobType jt, Boolean isEnabled); + + public static class EmailSettings { + + private final Boolean emailNotificationsEnabled; + private final String emailRecipients; + private final Boolean emailForEveryUnstableBuild; + private final Boolean emailSendToIndividuals; + private final Boolean emailPerModuleEmail; + + public EmailSettings() { + this(false, "", false, false, false); + } + + public EmailSettings(Boolean emailNotificationsEnabled, + String emailRecipients, Boolean emailForEveryUnstableBuild, + Boolean emailSendToIndividuals, Boolean emailPerModuleEmail) { + this.emailNotificationsEnabled = emailNotificationsEnabled; + this.emailRecipients = emailRecipients; + this.emailForEveryUnstableBuild = emailForEveryUnstableBuild; + this.emailSendToIndividuals = emailSendToIndividuals; + this.emailPerModuleEmail = emailPerModuleEmail; + } + + public Boolean getEmailNotificationsEnabled() { + return emailNotificationsEnabled; + } + + public String getEmailRecipients() { + return emailRecipients; + } + + public Boolean getEmailForEveryUnstableBuild() { + return emailForEveryUnstableBuild; + } + + public Boolean getEmailSendToIndividuals() { + return emailSendToIndividuals; + } + + public Boolean getEmailPerModuleEmail() { + return emailPerModuleEmail; + } + } } \ No newline at end of file diff --git a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsClientManager.java b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsClientManager.java index 948fdc4..bee6f58 100644 --- a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsClientManager.java +++ b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsClientManager.java @@ -16,22 +16,27 @@ import java.net.URI; import java.net.URISyntaxException; +import com.atlassian.stash.repository.Repository; import com.offbytwo.jenkins.JenkinsServer; import com.palantir.stash.stashbot.persistence.JenkinsServerConfiguration; import com.palantir.stash.stashbot.persistence.RepositoryConfiguration; /** - * This class exists to encapsulate the jenkins client library and make mocking/testing easier. - * + * This class exists to encapsulate the jenkins client library and make + * mocking/testing easier. + * * TODO: cache jenkins server object? - * + * * @author cmyers - * + * */ public class JenkinsClientManager { - public JenkinsServer getJenkinsServer(JenkinsServerConfiguration jsc, RepositoryConfiguration rc) - throws URISyntaxException { - return new JenkinsServer(new URI(jsc.getUrl()), jsc.getUsername(), jsc.getPassword()); - } + public JenkinsServer getJenkinsServer(JenkinsServerConfiguration jsc, + RepositoryConfiguration rc, Repository r) throws URISyntaxException { + + return new JenkinsServer(new URI(jsc.getUrlForRepo(r)), + jsc.getUsername(), jsc.getPassword()); + } + } diff --git a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java index d7a34e8..c5fdffa 100644 --- a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java +++ b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java @@ -56,483 +56,496 @@ public class JenkinsManager implements DisposableBean { - private final ConfigurationPersistenceService cpm; - private final JobTemplateManager jtm; - private final JenkinsJobXmlFormatter xmlFormatter; - private final JenkinsClientManager jenkinsClientManager; - private final RepositoryService repositoryService; - private final StashbotUrlBuilder sub; - private final Logger log; - private final PluginLoggerFactory lf; - private final SecurityService ss; - private final UserService us; - private final UserManager um; - private final ExecutorService es; - - public JenkinsManager(RepositoryService repositoryService, - ConfigurationPersistenceService cpm, JobTemplateManager jtm, JenkinsJobXmlFormatter xmlFormatter, - JenkinsClientManager jenkisnClientManager, StashbotUrlBuilder sub, PluginLoggerFactory lf, SecurityService ss, - UserService us, UserManager um) { - this.repositoryService = repositoryService; - this.cpm = cpm; - this.jtm = jtm; - this.xmlFormatter = xmlFormatter; - this.jenkinsClientManager = jenkisnClientManager; - this.sub = sub; - this.lf = lf; - this.log = lf.getLoggerForThis(this); - this.ss = ss; - this.us = us; - this.um = um; - this.es = Executors.newCachedThreadPool(); - } - - public void updateRepo(Repository repo) { - try { - Callable visit = new UpdateAllRepositoryVisitor( - jenkinsClientManager, jtm, cpm, repo, lf); - visit.call(); - } catch (Exception e) { - log.error( - "Exception while attempting to create missing jobs for a repo: ", - e); - } - } - - public void createJob(Repository repo, JobTemplate jobTemplate) { - try { - final RepositoryConfiguration rc = cpm - .getRepositoryConfigurationForRepository(repo); - final JenkinsServerConfiguration jsc = cpm - .getJenkinsServerConfiguration(rc.getJenkinsServerName()); - final JenkinsServer jenkinsServer = jenkinsClientManager - .getJenkinsServer(jsc, rc); - final String jobName = jobTemplate.getBuildNameFor(repo); - - // If we try to create a job which already exists, we still get a - // 200... so we should check first to make - // sure it doesn't already exist - Map jobMap = jenkinsServer.getJobs(); - - if (jobMap.containsKey(jobName)) { - throw new IllegalArgumentException("Job " + jobName - + " already exists"); - } - - String xml = xmlFormatter.generateJobXml(jobTemplate, repo); - - log.trace("Sending XML to jenkins to create job: " + xml); - jenkinsServer.createJob(jobName, xml); - } catch (IOException e) { - // TODO: something other than just rethrow? - throw new RuntimeException(e); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - /** - * This method IGNORES the current job XML, and regenerates it from scratch, and posts it. If any changes were made - * to the job directly via jenkins UI, this will overwrite those changes. - * - * @param repo - * @param buildType - */ - public void updateJob(Repository repo, JobTemplate jobTemplate) { - try { - final RepositoryConfiguration rc = cpm - .getRepositoryConfigurationForRepository(repo); - final JenkinsServerConfiguration jsc = cpm - .getJenkinsServerConfiguration(rc.getJenkinsServerName()); - final JenkinsServer jenkinsServer = jenkinsClientManager - .getJenkinsServer(jsc, rc); - final String jobName = jobTemplate.getBuildNameFor(repo); - - // If we try to create a job which already exists, we still get a - // 200... so we should check first to make - // sure it doesn't already exist - Map jobMap = jenkinsServer.getJobs(); - - String xml = xmlFormatter.generateJobXml(jobTemplate, repo); - - if (jobMap.containsKey(jobName)) { - if (!rc.getPreserveJenkinsJobConfig()) { - log.trace("Sending XML to jenkins to update job: " + xml); - jenkinsServer.updateJob(jobName, xml); - } else { - log.trace("Skipping sending XML to jenkins. Repo Config is set to preserve jenkins job config."); - } - return; - } - - log.trace("Sending XML to jenkins to create job: " + xml); - jenkinsServer.createJob(jobName, xml); - } catch (IOException e) { - // TODO: something other than just rethrow? - throw new RuntimeException(e); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - public void triggerBuild(final Repository repo, final JobType jobType, - final String hashToBuild, final String buildRef) { - - final String username = um.getRemoteUser().getUsername(); - final StashUser su = us.findUserByNameOrEmail(username); - - es.submit(new Callable() { - - @Override - public Void call() throws Exception { - // TODO: See if we can do something like StateTransferringExecutorService here instead - ss.impersonating(su, "Running as user '" + username + "' in alternate thread asynchronously") - .call(new Operation() { - - @Override - public Void perform() throws Exception { - synchronousTriggerBuild(repo, jobType, hashToBuild, buildRef); - return null; - } - }); - return null; - }; - }); - } - - public void triggerBuild(final Repository repo, final JobType jobType, - final PullRequest pr) { - - final String username = um.getRemoteUser().getUsername(); - final StashUser su = us.findUserByNameOrEmail(username); - - es.submit(new Callable() { - - @Override - public Void call() throws Exception { - // TODO: See if we can do something like StateTransferringExecutorService here instead - ss.impersonating(su, "Running as user '" + username + "' in alternate thread asynchronously") - .call(new Operation() { - - @Override - public Void perform() throws Exception { - synchronousTriggerBuild(repo, jobType, pr); - return null; - } - }); - return null; - }; - }); - } - - public void synchronousTriggerBuild(Repository repo, JobType jobType, - String hashToBuild, String buildRef) { - try { - RepositoryConfiguration rc = cpm - .getRepositoryConfigurationForRepository(repo); - JenkinsServerConfiguration jsc = cpm - .getJenkinsServerConfiguration(rc.getJenkinsServerName()); - JobTemplate jt = jtm.getJobTemplate(jobType, rc); - - String jenkinsBuildId = jt.getBuildNameFor(repo); - String url = jsc.getUrl(); - String user = jsc.getUsername(); - String password = jsc.getPassword(); - - log.info("Triggering jenkins build id " + jenkinsBuildId - + " on hash " + hashToBuild + " (" + user + "@" + url - + " pw: " + password.replaceAll(".", "*") + ")"); - - final JenkinsServer js = jenkinsClientManager.getJenkinsServer(jsc, - rc); - Map jobMap = js.getJobs(); - String key = jt.getBuildNameFor(repo); - - if (!jobMap.containsKey(key)) { - throw new RuntimeException("Build doesn't exist: " + key); - } - - Builder builder = ImmutableMap.builder(); - builder.put("buildHead", hashToBuild); - builder.put("repoId", repo.getId().toString()); - if (buildRef != null) { - builder.put("buildRef", buildRef); - } - - jobMap.get(key).build(builder.build()); - - } catch (SQLException e) { - throw new RuntimeException(e); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } catch (HttpResponseException e) { // subclass of IOException thrown by - // client - if (e.getStatusCode() == 302) { - // BUG in client - this isn't really an error, assume the build - // triggered ok and this is just a redirect - // to some URL after the fact. - return; - } - // For other HTTP errors, log it for easier debugging - log.error( - "HTTP Error (resp code " - + Integer.toString(e.getStatusCode()) + ")", e); - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public void synchronousTriggerBuild(Repository repo, JobType jobType, - PullRequest pullRequest) { - - try { - String pullRequestId = pullRequest.getId().toString(); - String hashToBuild = pullRequest.getToRef().getLatestChangeset(); - - RepositoryConfiguration rc = cpm - .getRepositoryConfigurationForRepository(repo); - JenkinsServerConfiguration jsc = cpm - .getJenkinsServerConfiguration(rc.getJenkinsServerName()); - JobTemplate jt = jtm.getJobTemplate(jobType, rc); - - String jenkinsBuildId = jt.getBuildNameFor(repo); - String url = jsc.getUrl(); - String user = jsc.getUsername(); - String password = jsc.getPassword(); - - log.info("Triggering jenkins build id " + jenkinsBuildId - + " on hash " + hashToBuild + " (" + user + "@" + url - + " pw: " + password.replaceAll(".", "*") + ")"); - - final JenkinsServer js = jenkinsClientManager.getJenkinsServer(jsc, - rc); - Map jobMap = js.getJobs(); - String key = jt.getBuildNameFor(repo); - - if (!jobMap.containsKey(key)) { - throw new RuntimeException("Build doesn't exist: " + key); - } - - Builder builder = ImmutableMap.builder(); - builder.put("repoId", repo.getId().toString()); - if (pullRequest != null) { - log.debug("Determined pullRequestId " + pullRequestId); - builder.put("pullRequestId", pullRequestId); - // toRef is always present in the repo - builder.put("buildHead", pullRequest.getToRef() - .getLatestChangeset().toString()); - // fromRef may be in a different repo - builder.put("mergeRef", pullRequest.getFromRef().getId()); - builder.put("buildRef", pullRequest.getToRef().getId()); - builder.put("mergeRefUrl", sub.buildCloneUrl(pullRequest.getFromRef().getRepository(), jsc)); - builder.put("mergeHead", pullRequest.getFromRef() - .getLatestChangeset().toString()); - } - - jobMap.get(key).build(builder.build()); - } catch (SQLException e) { - throw new RuntimeException(e); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } catch (HttpResponseException e) { // subclass of IOException thrown by - // client - if (e.getStatusCode() == 302) { - // BUG in client - this isn't really an error, assume the build - // triggered ok and this is just a redirect - // to some URL after the fact. - return; - } - // For other HTTP errors, log it for easier debugging - log.error( - "HTTP Error (resp code " - + Integer.toString(e.getStatusCode()) + ")", e); - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * Code to ensure a given repository has plans that exist in jenkins. - * - * @author cmyers - */ - class CreateMissingRepositoryVisitor implements Callable { - - private final JenkinsClientManager jcm; - private final JobTemplateManager jtm; - private final ConfigurationPersistenceService cpm; - private final Repository r; - private final Logger log; - - public CreateMissingRepositoryVisitor( - JenkinsClientManager jcm, JobTemplateManager jtm, - ConfigurationPersistenceService cpm, Repository r, - PluginLoggerFactory lf) { - this.jcm = jcm; - this.jtm = jtm; - this.cpm = cpm; - this.r = r; - this.log = lf.getLoggerForThis(this); - } - - @Override - public Void call() throws Exception { - RepositoryConfiguration rc = cpm - .getRepositoryConfigurationForRepository(r); - // may someday require repo also... - JenkinsServerConfiguration jsc = cpm - .getJenkinsServerConfiguration(rc.getJenkinsServerName()); - - if (!rc.getCiEnabled()) - return null; - - // make sure jobs exist - List templates = jtm.getJenkinsJobsForRepository(rc); - JenkinsServer js = jcm.getJenkinsServer(jsc, rc); - Map jobs = js.getJobs(); - - for (JobTemplate template : templates) { - if (!jobs.containsKey(template.getBuildNameFor(r))) { - log.info("Creating " + template.getName() - + " job for repo " + r.toString()); - createJob(r, template); - } - } - return null; - } - } - - public void createMissingJobs() { - - ExecutorService es = Executors.newCachedThreadPool(); - List> futures = new LinkedList>(); - - PageRequest pageReq = new PageRequestImpl(0, 500); - Page p = repositoryService.findAll(pageReq); - while (true) { - for (Repository r : p.getValues()) { - Future f = es.submit(new CreateMissingRepositoryVisitor( - jenkinsClientManager, jtm, cpm, r, lf)); - futures.add(f); - } - if (p.getIsLastPage()) - break; - pageReq = p.getNextPageRequest(); - p = repositoryService.findAll(pageReq); - } - for (Future f : futures) { - try { - f.get(); // don't care about return, just catch exceptions - } catch (ExecutionException e) { - log.error( - "Exception while attempting to create missing jobs for a repo: ", - e); - } catch (InterruptedException e) { - log.error("Interrupted: this shouldn't happen", e); - } - } - } - - /** - * Code to ensure a given repository has plans that exist in jenkins. - * - * @author cmyers - */ - class UpdateAllRepositoryVisitor implements Callable { - - private final JenkinsClientManager jcm; - private final JobTemplateManager jtm; - private final ConfigurationPersistenceService cpm; - private final Repository r; - private final Logger log; - - public UpdateAllRepositoryVisitor( - JenkinsClientManager jcm, JobTemplateManager jtm, - ConfigurationPersistenceService cpm, Repository r, - PluginLoggerFactory lf) { - this.jcm = jcm; - this.jtm = jtm; - this.cpm = cpm; - this.r = r; - this.log = lf.getLoggerForThis(this); - } - - @Override - public Void call() throws Exception { - RepositoryConfiguration rc = cpm - .getRepositoryConfigurationForRepository(r); - // may someday require repo also... - JenkinsServerConfiguration jsc = cpm - .getJenkinsServerConfiguration(rc.getJenkinsServerName()); - - if (!rc.getCiEnabled()) - return null; - - // make sure jobs are up to date - List templates = jtm.getJenkinsJobsForRepository(rc); - JenkinsServer js = jcm.getJenkinsServer(jsc, rc); - Map jobs = js.getJobs(); - for (JobTemplate jobTemplate : templates) { - if (!jobs.containsKey(jobTemplate.getBuildNameFor(r))) { - log.info("Creating " + jobTemplate.getName() - + " job for repo " + r.toString()); - createJob(r, jobTemplate); - } else { - // update job - log.info("Updating " + jobTemplate.getName() - + " job for repo " + r.toString()); - updateJob(r, jobTemplate); - } - } - return null; - } - } - - public void updateAllJobs() { - - ExecutorService es = Executors.newCachedThreadPool(); - List> futures = new LinkedList>(); - - PageRequest pageReq = new PageRequestImpl(0, 500); - Page p = repositoryService.findAll(pageReq); - while (true) { - for (Repository r : p.getValues()) { - Future f = es.submit(new UpdateAllRepositoryVisitor( - jenkinsClientManager, jtm, cpm, r, lf)); - futures.add(f); - } - if (p.getIsLastPage()) - break; - pageReq = p.getNextPageRequest(); - p = repositoryService.findAll(pageReq); - } - for (Future f : futures) { - try { - f.get(); // don't care about return, just catch exceptions - } catch (ExecutionException e) { - log.error( - "Exception while attempting to create missing jobs for a repo: ", - e); - } catch (InterruptedException e) { - log.error("Interrupted: this shouldn't happen", e); - } - } - } - - @Override - public void destroy() throws Exception { - // on a plugin upgrade or whatever, we want to make sure all tasks get executed. - es.shutdown(); - // This might be stupid. I'm aware. But the glorious unit tests say I need it. - while (!es.isTerminated()) { - Thread.sleep(50); - } - } + private final ConfigurationPersistenceService cpm; + private final JobTemplateManager jtm; + private final JenkinsJobXmlFormatter xmlFormatter; + private final JenkinsClientManager jenkinsClientManager; + private final RepositoryService repositoryService; + private final StashbotUrlBuilder sub; + private final Logger log; + private final PluginLoggerFactory lf; + private final SecurityService ss; + private final UserService us; + private final UserManager um; + private final ExecutorService es; + + public JenkinsManager(RepositoryService repositoryService, + ConfigurationPersistenceService cpm, JobTemplateManager jtm, + JenkinsJobXmlFormatter xmlFormatter, + JenkinsClientManager jenkisnClientManager, StashbotUrlBuilder sub, + PluginLoggerFactory lf, SecurityService ss, UserService us, + UserManager um) { + this.repositoryService = repositoryService; + this.cpm = cpm; + this.jtm = jtm; + this.xmlFormatter = xmlFormatter; + this.jenkinsClientManager = jenkisnClientManager; + this.sub = sub; + this.lf = lf; + this.log = lf.getLoggerForThis(this); + this.ss = ss; + this.us = us; + this.um = um; + this.es = Executors.newCachedThreadPool(); + } + + public void updateRepo(Repository repo) { + try { + Callable visit = new UpdateAllRepositoryVisitor( + jenkinsClientManager, jtm, cpm, repo, lf); + visit.call(); + } catch (Exception e) { + log.error( + "Exception while attempting to create missing jobs for a repo: ", + e); + } + } + + public void createJob(Repository repo, JobTemplate jobTemplate) { + try { + final RepositoryConfiguration rc = cpm + .getRepositoryConfigurationForRepository(repo); + final JenkinsServerConfiguration jsc = cpm + .getJenkinsServerConfiguration(rc.getJenkinsServerName()); + final JenkinsServer jenkinsServer = jenkinsClientManager + .getJenkinsServer(jsc, rc, repo); + final String jobName = jobTemplate.getBuildNameFor(repo); + + // If we try to create a job which already exists, we still get a + // 200... so we should check first to make + // sure it doesn't already exist + Map jobMap = jenkinsServer.getJobs(); + + if (jobMap.containsKey(jobName)) { + throw new IllegalArgumentException("Job " + jobName + + " already exists"); + } + + String xml = xmlFormatter.generateJobXml(jobTemplate, repo); + + log.trace("Sending XML to jenkins to create job: " + xml); + jenkinsServer.createJob(jobName, xml); + } catch (IOException e) { + // TODO: something other than just rethrow? + throw new RuntimeException(e); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + /** + * This method IGNORES the current job XML, and regenerates it from scratch, + * and posts it. If any changes were made to the job directly via jenkins + * UI, this will overwrite those changes. + * + * @param repo + * @param buildType + */ + public void updateJob(Repository repo, JobTemplate jobTemplate) { + try { + final RepositoryConfiguration rc = cpm + .getRepositoryConfigurationForRepository(repo); + final JenkinsServerConfiguration jsc = cpm + .getJenkinsServerConfiguration(rc.getJenkinsServerName()); + final JenkinsServer jenkinsServer = jenkinsClientManager + .getJenkinsServer(jsc, rc, repo); + final String jobName = jobTemplate.getBuildNameFor(repo); + + // If we try to create a job which already exists, we still get a + // 200... so we should check first to make + // sure it doesn't already exist + Map jobMap = jenkinsServer.getJobs(); + + String xml = xmlFormatter.generateJobXml(jobTemplate, repo); + + if (jobMap.containsKey(jobName)) { + if (!rc.getPreserveJenkinsJobConfig()) { + log.trace("Sending XML to jenkins to update job: " + xml); + jenkinsServer.updateJob(jobName, xml); + } else { + log.trace("Skipping sending XML to jenkins. Repo Config is set to preserve jenkins job config."); + } + return; + } + + log.trace("Sending XML to jenkins to create job: " + xml); + jenkinsServer.createJob(jobName, xml); + } catch (IOException e) { + // TODO: something other than just rethrow? + throw new RuntimeException(e); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public void triggerBuild(final Repository repo, final JobType jobType, + final String hashToBuild, final String buildRef) { + + final String username = um.getRemoteUser().getUsername(); + final StashUser su = us.findUserByNameOrEmail(username); + + es.submit(new Callable() { + + @Override + public Void call() throws Exception { + // TODO: See if we can do something like + // StateTransferringExecutorService here instead + ss.impersonating( + su, + "Running as user '" + username + + "' in alternate thread asynchronously").call( + new Operation() { + + @Override + public Void perform() throws Exception { + synchronousTriggerBuild(repo, jobType, + hashToBuild, buildRef); + return null; + } + }); + return null; + }; + }); + } + + public void triggerBuild(final Repository repo, final JobType jobType, + final PullRequest pr) { + + final String username = um.getRemoteUser().getUsername(); + final StashUser su = us.findUserByNameOrEmail(username); + + es.submit(new Callable() { + + @Override + public Void call() throws Exception { + // TODO: See if we can do something like + // StateTransferringExecutorService here instead + ss.impersonating( + su, + "Running as user '" + username + + "' in alternate thread asynchronously").call( + new Operation() { + + @Override + public Void perform() throws Exception { + synchronousTriggerBuild(repo, jobType, pr); + return null; + } + }); + return null; + }; + }); + } + + public void synchronousTriggerBuild(Repository repo, JobType jobType, + String hashToBuild, String buildRef) { + try { + RepositoryConfiguration rc = cpm + .getRepositoryConfigurationForRepository(repo); + JenkinsServerConfiguration jsc = cpm + .getJenkinsServerConfiguration(rc.getJenkinsServerName()); + JobTemplate jt = jtm.getJobTemplate(jobType, rc); + + String jenkinsBuildId = jt.getBuildNameFor(repo); + String url = jsc.getUrlForRepo(repo); + String user = jsc.getUsername(); + String password = jsc.getPassword(); + + log.info("Triggering jenkins build id " + jenkinsBuildId + + " on hash " + hashToBuild + " (" + user + "@" + url + + " pw: " + password.replaceAll(".", "*") + ")"); + + final JenkinsServer js = jenkinsClientManager.getJenkinsServer(jsc, + rc, repo); + Map jobMap = js.getJobs(); + String key = jt.getBuildNameFor(repo); + + if (!jobMap.containsKey(key)) { + throw new RuntimeException("Build doesn't exist: " + key); + } + + Builder builder = ImmutableMap.builder(); + builder.put("buildHead", hashToBuild); + builder.put("repoId", repo.getId().toString()); + if (buildRef != null) { + builder.put("buildRef", buildRef); + } + + jobMap.get(key).build(builder.build()); + + } catch (SQLException e) { + throw new RuntimeException(e); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } catch (HttpResponseException e) { // subclass of IOException thrown by + // client + if (e.getStatusCode() == 302) { + // BUG in client - this isn't really an error, assume the build + // triggered ok and this is just a redirect + // to some URL after the fact. + return; + } + // For other HTTP errors, log it for easier debugging + log.error( + "HTTP Error (resp code " + + Integer.toString(e.getStatusCode()) + ")", e); + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void synchronousTriggerBuild(Repository repo, JobType jobType, + PullRequest pullRequest) { + + try { + String pullRequestId = pullRequest.getId().toString(); + String hashToBuild = pullRequest.getToRef().getLatestChangeset(); + + RepositoryConfiguration rc = cpm + .getRepositoryConfigurationForRepository(repo); + JenkinsServerConfiguration jsc = cpm + .getJenkinsServerConfiguration(rc.getJenkinsServerName()); + JobTemplate jt = jtm.getJobTemplate(jobType, rc); + + String jenkinsBuildId = jt.getBuildNameFor(repo); + String url = jsc.getUrlForRepo(repo); + String user = jsc.getUsername(); + String password = jsc.getPassword(); + + log.info("Triggering jenkins build id " + jenkinsBuildId + + " on hash " + hashToBuild + " (" + user + "@" + url + + " pw: " + password.replaceAll(".", "*") + ")"); + + final JenkinsServer js = jenkinsClientManager.getJenkinsServer(jsc, + rc, repo); + Map jobMap = js.getJobs(); + String key = jt.getBuildNameFor(repo); + + if (!jobMap.containsKey(key)) { + throw new RuntimeException("Build doesn't exist: " + key); + } + + Builder builder = ImmutableMap.builder(); + builder.put("repoId", repo.getId().toString()); + if (pullRequest != null) { + log.debug("Determined pullRequestId " + pullRequestId); + builder.put("pullRequestId", pullRequestId); + // toRef is always present in the repo + builder.put("buildHead", pullRequest.getToRef() + .getLatestChangeset().toString()); + // fromRef may be in a different repo + builder.put("mergeRef", pullRequest.getFromRef().getId()); + builder.put("buildRef", pullRequest.getToRef().getId()); + builder.put("mergeRefUrl", sub.buildCloneUrl(pullRequest + .getFromRef().getRepository(), jsc)); + builder.put("mergeHead", pullRequest.getFromRef() + .getLatestChangeset().toString()); + } + + jobMap.get(key).build(builder.build()); + } catch (SQLException e) { + throw new RuntimeException(e); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } catch (HttpResponseException e) { // subclass of IOException thrown by + // client + if (e.getStatusCode() == 302) { + // BUG in client - this isn't really an error, assume the build + // triggered ok and this is just a redirect + // to some URL after the fact. + return; + } + // For other HTTP errors, log it for easier debugging + log.error( + "HTTP Error (resp code " + + Integer.toString(e.getStatusCode()) + ")", e); + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Code to ensure a given repository has plans that exist in jenkins. + * + * @author cmyers + */ + class CreateMissingRepositoryVisitor implements Callable { + + private final JenkinsClientManager jcm; + private final JobTemplateManager jtm; + private final ConfigurationPersistenceService cpm; + private final Repository r; + private final Logger log; + + public CreateMissingRepositoryVisitor(JenkinsClientManager jcm, + JobTemplateManager jtm, ConfigurationPersistenceService cpm, + Repository r, PluginLoggerFactory lf) { + this.jcm = jcm; + this.jtm = jtm; + this.cpm = cpm; + this.r = r; + this.log = lf.getLoggerForThis(this); + } + + @Override + public Void call() throws Exception { + RepositoryConfiguration rc = cpm + .getRepositoryConfigurationForRepository(r); + // may someday require repo also... + JenkinsServerConfiguration jsc = cpm + .getJenkinsServerConfiguration(rc.getJenkinsServerName()); + + if (!rc.getCiEnabled()) + return null; + + // make sure jobs exist + List templates = jtm.getJenkinsJobsForRepository(rc); + JenkinsServer js = jcm.getJenkinsServer(jsc, rc, this.r); + Map jobs = js.getJobs(); + + for (JobTemplate template : templates) { + if (!jobs.containsKey(template.getBuildNameFor(r))) { + log.info("Creating " + template.getName() + + " job for repo " + r.toString()); + createJob(r, template); + } + } + return null; + } + } + + public void createMissingJobs() { + + ExecutorService es = Executors.newCachedThreadPool(); + List> futures = new LinkedList>(); + + PageRequest pageReq = new PageRequestImpl(0, 500); + Page p = repositoryService.findAll(pageReq); + while (true) { + for (Repository r : p.getValues()) { + Future f = es.submit(new CreateMissingRepositoryVisitor( + jenkinsClientManager, jtm, cpm, r, lf)); + futures.add(f); + } + if (p.getIsLastPage()) + break; + pageReq = p.getNextPageRequest(); + p = repositoryService.findAll(pageReq); + } + for (Future f : futures) { + try { + f.get(); // don't care about return, just catch exceptions + } catch (ExecutionException e) { + log.error( + "Exception while attempting to create missing jobs for a repo: ", + e); + } catch (InterruptedException e) { + log.error("Interrupted: this shouldn't happen", e); + } + } + } + + /** + * Code to ensure a given repository has plans that exist in jenkins. + * + * @author cmyers + */ + class UpdateAllRepositoryVisitor implements Callable { + + private final JenkinsClientManager jcm; + private final JobTemplateManager jtm; + private final ConfigurationPersistenceService cpm; + private final Repository r; + private final Logger log; + + public UpdateAllRepositoryVisitor(JenkinsClientManager jcm, + JobTemplateManager jtm, ConfigurationPersistenceService cpm, + Repository r, PluginLoggerFactory lf) { + this.jcm = jcm; + this.jtm = jtm; + this.cpm = cpm; + this.r = r; + this.log = lf.getLoggerForThis(this); + } + + @Override + public Void call() throws Exception { + RepositoryConfiguration rc = cpm + .getRepositoryConfigurationForRepository(r); + // may someday require repo also... + JenkinsServerConfiguration jsc = cpm + .getJenkinsServerConfiguration(rc.getJenkinsServerName()); + + if (!rc.getCiEnabled()) + return null; + + // make sure jobs are up to date + List templates = jtm.getJenkinsJobsForRepository(rc); + JenkinsServer js = jcm.getJenkinsServer(jsc, rc, this.r); + Map jobs = js.getJobs(); + for (JobTemplate jobTemplate : templates) { + if (!jobs.containsKey(jobTemplate.getBuildNameFor(r))) { + log.info("Creating " + jobTemplate.getName() + + " job for repo " + r.toString()); + createJob(r, jobTemplate); + } else { + // update job + log.info("Updating " + jobTemplate.getName() + + " job for repo " + r.toString()); + updateJob(r, jobTemplate); + } + } + return null; + } + } + + public void updateAllJobs() { + + ExecutorService es = Executors.newCachedThreadPool(); + List> futures = new LinkedList>(); + + PageRequest pageReq = new PageRequestImpl(0, 500); + Page p = repositoryService.findAll(pageReq); + while (true) { + for (Repository r : p.getValues()) { + Future f = es.submit(new UpdateAllRepositoryVisitor( + jenkinsClientManager, jtm, cpm, r, lf)); + futures.add(f); + } + if (p.getIsLastPage()) + break; + pageReq = p.getNextPageRequest(); + p = repositoryService.findAll(pageReq); + } + for (Future f : futures) { + try { + f.get(); // don't care about return, just catch exceptions + } catch (ExecutionException e) { + log.error( + "Exception while attempting to create missing jobs for a repo: ", + e); + } catch (InterruptedException e) { + log.error("Interrupted: this shouldn't happen", e); + } + } + } + + @Override + public void destroy() throws Exception { + // on a plugin upgrade or whatever, we want to make sure all tasks get + // executed. + es.shutdown(); + // This might be stupid. I'm aware. But the glorious unit tests say I + // need it. + while (!es.isTerminated()) { + Thread.sleep(50); + } + } } diff --git a/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java b/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java index 4252b5b..1369463 100644 --- a/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java +++ b/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java @@ -19,10 +19,12 @@ import net.java.ao.Mutator; import net.java.ao.Preload; import net.java.ao.schema.Default; +import net.java.ao.schema.Ignore; import net.java.ao.schema.NotNull; import net.java.ao.schema.Table; import net.java.ao.schema.Unique; +import com.atlassian.stash.repository.Repository; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -31,153 +33,175 @@ @Implementation(JenkinsServerConfigurationImpl.class) public interface JenkinsServerConfiguration extends Entity { - static public enum AuthenticationMode { - // NOTE: when you add stuff here, edit StashbotUrlBuilder as well. - USERNAME_AND_PASSWORD(Constants.UAP_VALUE, "Username and Password"), - CREDENTIAL_MANUALLY_CONFIGURED(Constants.CMC_VALUE, "Manually Configured Credential UUID"); - - // TODO? - //CREDENTIAL_USERNAME_AND_PASSWORD(Constants.CUAP_VALUE), - //CREDENTIAL_SSH_KEY(Constants.CSSH_VALUE); - private final String description; - private final String mode; - - // This is necessary because AO annotations require static string constants - public static class Constants { - - public static final String UAP_VALUE = "USERNAME_AND_PASSWORD"; - public static final String CMC_VALUE = "CREDENTIAL_MANUALLY_CONFIGURED"; - //public static final String CUAP_VALUE = "CUAP"; - //public static final String CSSH_VALUE = "CSSH"; - } - - AuthenticationMode(String mode, String description) { - this.description = description; - this.mode = mode; - } - - public String getDescription() { - return description; - } - - public String getMode() { - return mode; - } - - public static AuthenticationMode fromMode(String mode) { - if (mode.equals(Constants.UAP_VALUE)) { - return USERNAME_AND_PASSWORD; - } - if (mode.equals(Constants.CMC_VALUE)) { - return CREDENTIAL_MANUALLY_CONFIGURED; - } - throw new IllegalArgumentException("invalid value for enum: " + mode); - } - - public static String toMode(AuthenticationMode am) { - return am.getMode(); - } - - /** - * Helper method for populating a dropdown option box with metadata - * - * @param selected - * @return - */ - public ImmutableMap getSelectListEntry(boolean selected) { - if (selected) { - return ImmutableMap.of("text", this.getDescription(), "value", this.toString(), "selected", "true"); - } else { - return ImmutableMap.of("text", this.getDescription(), "value", this.toString()); - } - } - - /** - * Helper method for populating a dropdown option box with metadata - * - * @param selected - * @return - */ - public static ImmutableList> getSelectList(AuthenticationMode selected) { - ImmutableList.Builder> builder = ImmutableList.builder(); - for (AuthenticationMode ae : AuthenticationMode.values()) { - if (selected != null && selected.equals(ae)) { - builder.add(ae.getSelectListEntry(true)); - } else { - builder.add(ae.getSelectListEntry(false)); - } - } - return builder.build(); - } - } - - @NotNull - @Unique - public String getName(); - - public void setName(String username); - - @NotNull - @Default("empty") - public String getUrl(); - - public void setUrl(String url); - - @NotNull - @Default("empty") - public String getUsername(); - - public void setUsername(String username); - - @NotNull - @Default("empty") - public String getPassword(); - - public void setPassword(String password); - - // New Credential handling - enum stored as Const TXT in the DB - @NotNull - @Default(AuthenticationMode.Constants.UAP_VALUE) - public String getAuthenticationModeStr(); - - public void setAuthenticationModeStr(String authMode); - - ///// - // These are implemented in JenkinsServerConfigurationImpl - so the user can use enums - ///// - public AuthenticationMode getAuthenticationMode(); - - public void setAuthenticationMode(AuthenticationMode authMode); - - @NotNull - @Default("empty") - public String getStashUsername(); - - public void setStashUsername(String stashUsername); - - @NotNull - @Default("empty") - public String getStashPassword(); - - public void setStashPassword(String stashPassword); - - /** - * Maximum number of verify builds to trigger when pushed all at once. This limit makes it so that if you push a - * chain of 100 new commits all at once, instead of saturating your build hardware, only the N most recent commits - * are built. Set to "0" for infinite. Default is 10. - */ - @NotNull - @Default("10") - public Integer getMaxVerifyChain(); - - public void setMaxVerifyChain(Integer max); - - // For security - allow a jenkins server config to be locked to non-system-admins - @NotNull - @Default("false") - @Accessor("LOCKED") - public Boolean getLocked(); - - @Mutator("LOCKED") - public void setLocked(Boolean isLocked); + static public enum AuthenticationMode { + // NOTE: when you add stuff here, edit StashbotUrlBuilder as well. + USERNAME_AND_PASSWORD(Constants.UAP_VALUE, "Username and Password"), CREDENTIAL_MANUALLY_CONFIGURED( + Constants.CMC_VALUE, "Manually Configured Credential UUID"); + + // TODO? + // CREDENTIAL_USERNAME_AND_PASSWORD(Constants.CUAP_VALUE), + // CREDENTIAL_SSH_KEY(Constants.CSSH_VALUE); + private final String description; + private final String mode; + + // This is necessary because AO annotations require static string + // constants + public static class Constants { + + public static final String UAP_VALUE = "USERNAME_AND_PASSWORD"; + public static final String CMC_VALUE = "CREDENTIAL_MANUALLY_CONFIGURED"; + // public static final String CUAP_VALUE = "CUAP"; + // public static final String CSSH_VALUE = "CSSH"; + } + + AuthenticationMode(String mode, String description) { + this.description = description; + this.mode = mode; + } + + public String getDescription() { + return description; + } + + public String getMode() { + return mode; + } + + public static AuthenticationMode fromMode(String mode) { + if (mode.equals(Constants.UAP_VALUE)) { + return USERNAME_AND_PASSWORD; + } + if (mode.equals(Constants.CMC_VALUE)) { + return CREDENTIAL_MANUALLY_CONFIGURED; + } + throw new IllegalArgumentException("invalid value for enum: " + + mode); + } + + public static String toMode(AuthenticationMode am) { + return am.getMode(); + } + + /** + * Helper method for populating a dropdown option box with metadata + * + * @param selected + * @return + */ + public ImmutableMap getSelectListEntry(boolean selected) { + if (selected) { + return ImmutableMap.of("text", this.getDescription(), "value", + this.toString(), "selected", "true"); + } else { + return ImmutableMap.of("text", this.getDescription(), "value", + this.toString()); + } + } + + /** + * Helper method for populating a dropdown option box with metadata + * + * @param selected + * @return + */ + public static ImmutableList> getSelectList( + AuthenticationMode selected) { + ImmutableList.Builder> builder = ImmutableList + .builder(); + for (AuthenticationMode ae : AuthenticationMode.values()) { + if (selected != null && selected.equals(ae)) { + builder.add(ae.getSelectListEntry(true)); + } else { + builder.add(ae.getSelectListEntry(false)); + } + } + return builder.build(); + } + } + + @NotNull + @Unique + public String getName(); + + public void setName(String username); + + @NotNull + @Default("empty") + public String getUrl(); + + public void setUrl(String url); + + @NotNull + @Default("empty") + public String getUsername(); + + public void setUsername(String username); + + @NotNull + @Default("empty") + public String getPassword(); + + public void setPassword(String password); + + // New Credential handling - enum stored as Const TXT in the DB + @NotNull + @Default(AuthenticationMode.Constants.UAP_VALUE) + public String getAuthenticationModeStr(); + + public void setAuthenticationModeStr(String authMode); + + // /// + // These are implemented in JenkinsServerConfigurationImpl - so the user can + // use enums + // /// + public AuthenticationMode getAuthenticationMode(); + + public void setAuthenticationMode(AuthenticationMode authMode); + + @NotNull + @Default("empty") + public String getStashUsername(); + + public void setStashUsername(String stashUsername); + + @NotNull + @Default("empty") + public String getStashPassword(); + + public void setStashPassword(String stashPassword); + + /** + * Maximum number of verify builds to trigger when pushed all at once. This + * limit makes it so that if you push a chain of 100 new commits all at + * once, instead of saturating your build hardware, only the N most recent + * commits are built. Set to "0" for infinite. Default is 10. + */ + @NotNull + @Default("10") + public Integer getMaxVerifyChain(); + + public void setMaxVerifyChain(Integer max); + + public String getPrefixTemplate(); + + public void setPrefixTemplate(String template); + + // Implemented in JenkinsServerConfigurationImpl - expands variables in + // template and appends to url. + @Ignore + public String getUrlForRepo(Repository r); + + @Ignore + @Deprecated + public void setUrlForRepo(String s); + + // For security - allow a jenkins server config to be locked to + // non-system-admins + @NotNull + @Default("false") + @Accessor("LOCKED") + public Boolean getLocked(); + + @Mutator("LOCKED") + public void setLocked(Boolean isLocked); } diff --git a/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfigurationImpl.java b/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfigurationImpl.java index 249ee58..8fd4736 100644 --- a/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfigurationImpl.java +++ b/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfigurationImpl.java @@ -13,21 +13,35 @@ // limitations under the License. package com.palantir.stash.stashbot.persistence; +import com.atlassian.stash.repository.Repository; import com.palantir.stash.stashbot.persistence.JenkinsServerConfiguration.AuthenticationMode; public class JenkinsServerConfigurationImpl { - private final JenkinsServerConfiguration jsc; + private final JenkinsServerConfiguration jsc; - public JenkinsServerConfigurationImpl(JenkinsServerConfiguration jsc) { - this.jsc = jsc; - } + public JenkinsServerConfigurationImpl(JenkinsServerConfiguration jsc) { + this.jsc = jsc; + } - public AuthenticationMode getAuthenticationMode() { - return AuthenticationMode.fromMode(jsc.getAuthenticationModeStr()); - } + public AuthenticationMode getAuthenticationMode() { + return AuthenticationMode.fromMode(jsc.getAuthenticationModeStr()); + } - public void setAuthenticationMode(AuthenticationMode authMode) { - jsc.setAuthenticationModeStr(authMode.getMode()); - } + public void setAuthenticationMode(AuthenticationMode authMode) { + jsc.setAuthenticationModeStr(authMode.getMode()); + } + + public String getUrlForRepo(Repository r) { + if (!jsc.getPrefixTemplate().equals("")) { + String template = jsc.getPrefixTemplate(); + template = template.replaceAll("\\$project", r.getProject() + .getKey()); + template = template.replaceAll("\\$repo", r.getSlug()); + + return jsc.getUrl() + template; + } else { + return jsc.getUrl(); + } + } } diff --git a/src/main/java/com/palantir/stash/stashbot/servlet/BuildSuccessReportingServlet.java b/src/main/java/com/palantir/stash/stashbot/servlet/BuildSuccessReportingServlet.java index 31808b7..6e0e1cc 100644 --- a/src/main/java/com/palantir/stash/stashbot/servlet/BuildSuccessReportingServlet.java +++ b/src/main/java/com/palantir/stash/stashbot/servlet/BuildSuccessReportingServlet.java @@ -53,289 +53,315 @@ public class BuildSuccessReportingServlet extends HttpServlet { - /** - * Handle information about build success / failure for a given hash and/or pull request - * - * URL is of the form BASE_URL/REPO_ID/TYPE/STATE/BUILD_NUMBER/BUILD_HEAD[/MERGE_HEAD /PULLREQUEST_ID]
- *
- * REPO_ID is the stash internal ID of the repository
- * TYPE is "verification" or "release" STATE is "successful", "failed", or "inprogress"
- * BUILD_NUMBER is the jenkins build number BUILD_HEAD is the sha1 hash that is being built
- * MERGE_HEAD/PULLREQUEST_ID is the (optional) sha1 hash that was merged into, along with the pull request ID
- * - */ - private static final long serialVersionUID = 1L; - private final Logger log; - - private final ConfigurationPersistenceService configurationPersistanceManager; - private final RepositoryService repositoryService; - private final BuildStatusService buildStatusService; - private final PullRequestService pullRequestService; - private final StashbotUrlBuilder ub; - private final JobTemplateManager jtm; - private final SecurityService ss; - private final UserService us; - - /** - * @deprecated Use - * {@link #BuildSuccessReportingServlet(ConfigurationPersistenceImpl,RepositoryService,BuildStatusService,PullRequestService,StashbotUrlBuilder,JobTemplateManager,SecurityService,PluginLoggerFactory)} - * instead - */ - @Deprecated - public BuildSuccessReportingServlet( - ConfigurationPersistenceService configurationPersistenceManager, - RepositoryService repositoryService, - BuildStatusService buildStatusService, - PullRequestService pullRequestService, StashbotUrlBuilder ub, - JobTemplateManager jtm, PluginLoggerFactory lf) { - this(configurationPersistenceManager, repositoryService, buildStatusService, pullRequestService, ub, jtm, - null, null, lf); - - } - - public BuildSuccessReportingServlet( - ConfigurationPersistenceService configurationPersistenceManager, - RepositoryService repositoryService, - BuildStatusService buildStatusService, - PullRequestService pullRequestService, StashbotUrlBuilder ub, - JobTemplateManager jtm, SecurityService ss, UserService us, PluginLoggerFactory lf) { - this.configurationPersistanceManager = configurationPersistenceManager; - this.repositoryService = repositoryService; - this.buildStatusService = buildStatusService; - this.pullRequestService = pullRequestService; - this.ub = ub; - this.jtm = jtm; - this.log = lf.getLoggerForThis(this); - this.ss = ss; - this.us = us; - } - - @Override - public void doGet(HttpServletRequest req, HttpServletResponse res) - throws ServletException, IOException { - try { - // Look at JenkinsManager class if you change this: - // final two arguments could be empty... - final String URL_FORMAT = - "BASE_URL/REPO_ID/TYPE/STATE/BUILD_NUMBER/BUILD_HEAD[/MERGE_HEAD/PULLREQUEST_ID]"; - final String pathInfo = req.getPathInfo(); - - final String[] parts = pathInfo.split("/"); - - if (parts.length != 6 && parts.length != 8) { - throw new IllegalArgumentException("The format of the URL is " - + URL_FORMAT); - } - final int repoId; - final RepositoryConfiguration rc; - try { - repoId = Integer.valueOf(parts[1]); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("The format of the URL is " - + URL_FORMAT, e); - } - - // This is necessary if we want unauthenticated users to be able to call this. *sigh* - RepoIdFetcherOperation getRepoId = new RepoIdFetcherOperation(repositoryService, repoId); - ss.withPermission(Permission.REPO_READ, "BUILD SUCCESS REPORT").call(getRepoId); - final Repository repo = getRepoId.getRepo(); - - rc = configurationPersistanceManager - .getRepositoryConfigurationForRepository(repo); - if (repo == null) { - throw new IllegalArgumentException( - "Unable to get a repository for id " + repoId); - } - - JobTemplate jt = jtm.fromString(rc, parts[2].toLowerCase()); - if (jt == null) { - throw new IllegalArgumentException( - "Unable to get a valid JobTemplate from " + parts[2]); - } - - final State state = BuildStatus.State.fromString(parts[3]); - if (state == null) { - throw new IllegalArgumentException( - "The state must be 'successful', 'failed', or 'inprogress'"); - } - - final long buildNumber; - try { - buildNumber = Long.parseLong(parts[4]); - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - "Unable to parse build number", e); - } - - // TODO: ensure this hash actually exists? - final String buildHead = parts[5]; - final String mergeHead; - final long pullRequestId; - final PullRequest pullRequest; - - final String retUrl; - if (parts.length == 8 && !parts[6].isEmpty() && !parts[7].isEmpty()) { - mergeHead = parts[6]; - try { - // This is a pull request, so add a comment - pullRequestId = Long.parseLong(parts[7]); - PullRequestFetcherOperation prfo = - new PullRequestFetcherOperation(pullRequestService, repoId, pullRequestId); - ss.withPermission(Permission.REPO_READ, "BUILD SUCCESS REPORT").call(prfo); - pullRequest = prfo.getPullRequest(); - - if (pullRequest == null) { - throw new IllegalArgumentException( - "Unable to find pull request for repo id " - + repo.getId().toString() + " pr id " - + Long.toString(pullRequestId)); - } - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - "Unable to parse pull request id " + parts[7], e); - } - retUrl = ub.getJenkinsTriggerUrl(repo, jt.getJobType(), - buildHead, pullRequest); - } else { - mergeHead = null; - pullRequestId = 0; - pullRequest = null; - retUrl = ub.getJenkinsTriggerUrl(repo, jt.getJobType(), - buildHead, null); - } - - if (mergeHead == null) { - BuildStatus bs; - bs = getSuccessStatus(repo, jt, state, buildNumber, buildHead); - log.debug("Registering build status for buildHead " + buildHead - + " " + bsToString(bs)); - BuildStatusAddOperation bssAdder = new BuildStatusAddOperation(buildStatusService, buildHead, bs); - // Yeah, I know what you are thinking... "Admin permission? To add a build status?" - // I tried REPO_WRITE and REPO_ADMIN and neither was enough, but this worked! - ss.withPermission(Permission.SYS_ADMIN, "BUILD SUCCESS REPORT").call(bssAdder); - printOutput(req, res); - return; - } - - // Update the metadata. We do this before adding the comment so that any listeners consuming - // comment events will have the updated state. - - // arg order for bools is started, success, override, failed - if (state.equals(State.SUCCESSFUL)) { - configurationPersistanceManager.setPullRequestMetadata( - pullRequest, mergeHead, buildHead, null, true, null, false); - } else if (state.equals(State.INPROGRESS)) { - configurationPersistanceManager.setPullRequestMetadata( - pullRequest, mergeHead, buildHead, true, false, null, null); - } else if (state.equals(State.FAILED)) { - configurationPersistanceManager.setPullRequestMetadata( - pullRequest, mergeHead, buildHead, null, false, null, true); - } - - // mergeHead is not null *and* pullRequest is not null if we reach - // here. - final StringBuffer sb = new StringBuffer(); - final String url = getJenkinsUrl(repo, jt, buildNumber); - - /* NOTE: mergeHead and buildHead are the reverse of what you might - * think, because we have to check out the "toRef" becasue it is - * the ref that is guaranteed to be in the correct repo. - * Nonetheless, buildHead is the commit that is being merged "into" - * the target branch, which is the mergeHead variable here. - */ - final int hashLength = 4; - final String shortMergeHead = mergeHead.substring(0, hashLength); - final String shortBuildHead = buildHead.substring(0, hashLength); - - final String mergeHeadUrl = ub.buildStashCommitUrl(repo, mergeHead); - final String buildHeadUrl = ub.buildStashCommitUrl(repo, buildHead); - - final String mergeHeadLink = "[" + shortMergeHead + "](" + mergeHeadUrl + ")"; - final String buildHeadLink = "[" + shortBuildHead + "](" + buildHeadUrl + ")"; - - final String consoleUrl = url + "/console"; - - sb.append("*[Build #" + buildNumber + "](" + url + ") "); - sb.append("(merging " + mergeHeadLink + " into " + buildHeadLink + ") "); - switch (state) { - case INPROGRESS: - sb.append("is in progress...*"); - break; - case SUCCESSFUL: - sb.append("has **passed ✓**.*"); - break; - case FAILED: - sb.append("has* **FAILED ✖**. "); - sb.append("([*Retrigger this build* ⟳](" + retUrl + ") *or* [*view console output* ☰](" - + consoleUrl + ").)"); - break; - } - - log.debug("Registering comment on pr for buildHead " + buildHead - + " mergeHead " + mergeHead); - // Still make comment so users can see links to build - PullRequestCommentAddOperation prcao = - new PullRequestCommentAddOperation(pullRequestService, repo.getId(), pullRequest.getId(), sb.toString()); - - // So in order to create comments, we have to do it AS some user. ss.doAsUser rather than ss.doWithPermission is the magic sauce here. - JenkinsServerConfiguration jsc = - configurationPersistanceManager.getJenkinsServerConfiguration(rc.getJenkinsServerName()); - StashUser user = us.findUserByNameOrEmail(jsc.getStashUsername()); - ss.impersonating(user, "BUILD SUCCESS REPORT").call(prcao); - - printOutput(req, res); - } catch (SQLException e) { - throw new RuntimeException("Unable to get configuration", e); - } catch (Exception e) { - throw new RuntimeException("Unable to report build status", e); - } - } - - private void printOutput(HttpServletRequest req, HttpServletResponse res) - throws IOException { - res.reset(); - res.setStatus(200); - res.setContentType("text/plain;charset=UTF-8"); - Writer w = res.getWriter(); - w.append("Status Updated"); - w.close(); - } - - private BuildStatus getSuccessStatus(Repository repo, JobTemplate jt, - State state, long buildNumber, String buildHead) - throws SQLException { - Date now = new Date(java.lang.System.currentTimeMillis()); - - DateFormat df = DateFormat.getDateInstance(); - // key will be the jenkins name - String key = jt.getBuildNameFor(repo); - String name = key + " (build " + Long.toString(buildNumber) + ")"; - String description = "Build " + Long.toString(buildNumber) + " " - + state.toString() + " at " + df.format(now); - String url = getJenkinsUrl(repo, jt, buildNumber); - BuildStatus bs = new InternalBuildStatus(state, name, name, url, - description, now); - return bs; - } - - private String getJenkinsUrl(Repository repo, JobTemplate jt, - long buildNumber) throws SQLException { - RepositoryConfiguration rc = configurationPersistanceManager - .getRepositoryConfigurationForRepository(repo); - JenkinsServerConfiguration jsc = configurationPersistanceManager - .getJenkinsServerConfiguration(rc.getJenkinsServerName()); - String key = jt.getBuildNameFor(repo); - String url = jsc.getUrl() + "/job/" + key + "/" - + Long.toString(buildNumber); - return url; - } - - private static String bsToString(BuildStatus bs) { - StringBuffer sb = new StringBuffer(); - sb.append("[BuildStatus "); - sb.append("Name:'" + bs.getKey() + "' "); - sb.append("Type:'" + bs.getName() + "' "); - sb.append("State:'" + bs.getState().toString() + "']"); - return sb.toString(); - } + /** + * Handle information about build success / failure for a given hash and/or + * pull request + * + * URL is of the form + * BASE_URL/REPO_ID/TYPE/STATE/BUILD_NUMBER/BUILD_HEAD[/MERGE_HEAD + * /PULLREQUEST_ID]
+ *
+ * REPO_ID is the stash internal ID of the repository
+ * TYPE is "verification" or "release" STATE is "successful", "failed", or + * "inprogress"
+ * BUILD_NUMBER is the jenkins build number BUILD_HEAD is the sha1 hash that + * is being built
+ * MERGE_HEAD/PULLREQUEST_ID is the (optional) sha1 hash that was merged + * into, along with the pull request ID
+ * + */ + private static final long serialVersionUID = 1L; + private final Logger log; + + private final ConfigurationPersistenceService configurationPersistanceManager; + private final RepositoryService repositoryService; + private final BuildStatusService buildStatusService; + private final PullRequestService pullRequestService; + private final StashbotUrlBuilder ub; + private final JobTemplateManager jtm; + private final SecurityService ss; + private final UserService us; + + /** + * @deprecated Use + * {@link #BuildSuccessReportingServlet(ConfigurationPersistenceImpl,RepositoryService,BuildStatusService,PullRequestService,StashbotUrlBuilder,JobTemplateManager,SecurityService,PluginLoggerFactory)} + * instead + */ + @Deprecated + public BuildSuccessReportingServlet( + ConfigurationPersistenceService configurationPersistenceManager, + RepositoryService repositoryService, + BuildStatusService buildStatusService, + PullRequestService pullRequestService, StashbotUrlBuilder ub, + JobTemplateManager jtm, PluginLoggerFactory lf) { + this(configurationPersistenceManager, repositoryService, + buildStatusService, pullRequestService, ub, jtm, null, null, lf); + + } + + public BuildSuccessReportingServlet( + ConfigurationPersistenceService configurationPersistenceManager, + RepositoryService repositoryService, + BuildStatusService buildStatusService, + PullRequestService pullRequestService, StashbotUrlBuilder ub, + JobTemplateManager jtm, SecurityService ss, UserService us, + PluginLoggerFactory lf) { + this.configurationPersistanceManager = configurationPersistenceManager; + this.repositoryService = repositoryService; + this.buildStatusService = buildStatusService; + this.pullRequestService = pullRequestService; + this.ub = ub; + this.jtm = jtm; + this.log = lf.getLoggerForThis(this); + this.ss = ss; + this.us = us; + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse res) + throws ServletException, IOException { + try { + // Look at JenkinsManager class if you change this: + // final two arguments could be empty... + final String URL_FORMAT = "BASE_URL/REPO_ID/TYPE/STATE/BUILD_NUMBER/BUILD_HEAD[/MERGE_HEAD/PULLREQUEST_ID]"; + final String pathInfo = req.getPathInfo(); + + final String[] parts = pathInfo.split("/"); + + if (parts.length != 6 && parts.length != 8) { + throw new IllegalArgumentException("The format of the URL is " + + URL_FORMAT); + } + final int repoId; + final RepositoryConfiguration rc; + try { + repoId = Integer.valueOf(parts[1]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("The format of the URL is " + + URL_FORMAT, e); + } + + // This is necessary if we want unauthenticated users to be able to + // call this. *sigh* + RepoIdFetcherOperation getRepoId = new RepoIdFetcherOperation( + repositoryService, repoId); + ss.withPermission(Permission.REPO_READ, "BUILD SUCCESS REPORT") + .call(getRepoId); + final Repository repo = getRepoId.getRepo(); + + rc = configurationPersistanceManager + .getRepositoryConfigurationForRepository(repo); + if (repo == null) { + throw new IllegalArgumentException( + "Unable to get a repository for id " + repoId); + } + + JobTemplate jt = jtm.fromString(rc, parts[2].toLowerCase()); + if (jt == null) { + throw new IllegalArgumentException( + "Unable to get a valid JobTemplate from " + parts[2]); + } + + final State state = BuildStatus.State.fromString(parts[3]); + if (state == null) { + throw new IllegalArgumentException( + "The state must be 'successful', 'failed', or 'inprogress'"); + } + + final long buildNumber; + try { + buildNumber = Long.parseLong(parts[4]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Unable to parse build number", e); + } + + // TODO: ensure this hash actually exists? + final String buildHead = parts[5]; + final String mergeHead; + final long pullRequestId; + final PullRequest pullRequest; + + final String retUrl; + if (parts.length == 8 && !parts[6].isEmpty() && !parts[7].isEmpty()) { + mergeHead = parts[6]; + try { + // This is a pull request, so add a comment + pullRequestId = Long.parseLong(parts[7]); + PullRequestFetcherOperation prfo = new PullRequestFetcherOperation( + pullRequestService, repoId, pullRequestId); + ss.withPermission(Permission.REPO_READ, + "BUILD SUCCESS REPORT").call(prfo); + pullRequest = prfo.getPullRequest(); + + if (pullRequest == null) { + throw new IllegalArgumentException( + "Unable to find pull request for repo id " + + repo.getId().toString() + " pr id " + + Long.toString(pullRequestId)); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Unable to parse pull request id " + parts[7], e); + } + retUrl = ub.getJenkinsTriggerUrl(repo, jt.getJobType(), + buildHead, pullRequest); + } else { + mergeHead = null; + pullRequestId = 0; + pullRequest = null; + retUrl = ub.getJenkinsTriggerUrl(repo, jt.getJobType(), + buildHead, null); + } + + if (mergeHead == null) { + BuildStatus bs; + bs = getSuccessStatus(repo, jt, state, buildNumber, buildHead); + log.debug("Registering build status for buildHead " + buildHead + + " " + bsToString(bs)); + BuildStatusAddOperation bssAdder = new BuildStatusAddOperation( + buildStatusService, buildHead, bs); + // Yeah, I know what you are thinking... + // "Admin permission? To add a build status?" + // I tried REPO_WRITE and REPO_ADMIN and neither was enough, but + // this worked! + ss.withPermission(Permission.SYS_ADMIN, "BUILD SUCCESS REPORT") + .call(bssAdder); + printOutput(req, res); + return; + } + + // Update the metadata. We do this before adding the comment so that + // any listeners consuming + // comment events will have the updated state. + + // arg order for bools is started, success, override, failed + if (state.equals(State.SUCCESSFUL)) { + configurationPersistanceManager.setPullRequestMetadata( + pullRequest, mergeHead, buildHead, null, true, null, + false); + } else if (state.equals(State.INPROGRESS)) { + configurationPersistanceManager.setPullRequestMetadata( + pullRequest, mergeHead, buildHead, true, false, null, + null); + } else if (state.equals(State.FAILED)) { + configurationPersistanceManager.setPullRequestMetadata( + pullRequest, mergeHead, buildHead, null, false, null, + true); + } + + // mergeHead is not null *and* pullRequest is not null if we reach + // here. + final StringBuffer sb = new StringBuffer(); + final String url = getJenkinsUrl(repo, jt, buildNumber); + + /* + * NOTE: mergeHead and buildHead are the reverse of what you might + * think, because we have to check out the "toRef" becasue it is the + * ref that is guaranteed to be in the correct repo. Nonetheless, + * buildHead is the commit that is being merged "into" the target + * branch, which is the mergeHead variable here. + */ + final int hashLength = 4; + final String shortMergeHead = mergeHead.substring(0, hashLength); + final String shortBuildHead = buildHead.substring(0, hashLength); + + final String mergeHeadUrl = ub.buildStashCommitUrl(repo, mergeHead); + final String buildHeadUrl = ub.buildStashCommitUrl(repo, buildHead); + + final String mergeHeadLink = "[" + shortMergeHead + "](" + + mergeHeadUrl + ")"; + final String buildHeadLink = "[" + shortBuildHead + "](" + + buildHeadUrl + ")"; + + final String consoleUrl = url + "/console"; + + sb.append("*[Build #" + buildNumber + "](" + url + ") "); + sb.append("(merging " + mergeHeadLink + " into " + buildHeadLink + + ") "); + switch (state) { + case INPROGRESS: + sb.append("is in progress...*"); + break; + case SUCCESSFUL: + sb.append("has **passed ✓**.*"); + break; + case FAILED: + sb.append("has* **FAILED ✖**. "); + sb.append("([*Retrigger this build* ⟳](" + retUrl + + ") *or* [*view console output* ☰](" + + consoleUrl + ").)"); + break; + } + + log.debug("Registering comment on pr for buildHead " + buildHead + + " mergeHead " + mergeHead); + // Still make comment so users can see links to build + PullRequestCommentAddOperation prcao = new PullRequestCommentAddOperation( + pullRequestService, repo.getId(), pullRequest.getId(), + sb.toString()); + + // So in order to create comments, we have to do it AS some user. + // ss.doAsUser rather than ss.doWithPermission is the magic sauce + // here. + JenkinsServerConfiguration jsc = configurationPersistanceManager + .getJenkinsServerConfiguration(rc.getJenkinsServerName()); + StashUser user = us.findUserByNameOrEmail(jsc.getStashUsername()); + ss.impersonating(user, "BUILD SUCCESS REPORT").call(prcao); + + printOutput(req, res); + } catch (SQLException e) { + throw new RuntimeException("Unable to get configuration", e); + } catch (Exception e) { + throw new RuntimeException("Unable to report build status", e); + } + } + + private void printOutput(HttpServletRequest req, HttpServletResponse res) + throws IOException { + res.reset(); + res.setStatus(200); + res.setContentType("text/plain;charset=UTF-8"); + Writer w = res.getWriter(); + w.append("Status Updated"); + w.close(); + } + + private BuildStatus getSuccessStatus(Repository repo, JobTemplate jt, + State state, long buildNumber, String buildHead) + throws SQLException { + Date now = new Date(java.lang.System.currentTimeMillis()); + + DateFormat df = DateFormat.getDateInstance(); + // key will be the jenkins name + String key = jt.getBuildNameFor(repo); + String name = key + " (build " + Long.toString(buildNumber) + ")"; + String description = "Build " + Long.toString(buildNumber) + " " + + state.toString() + " at " + df.format(now); + String url = getJenkinsUrl(repo, jt, buildNumber); + BuildStatus bs = new InternalBuildStatus(state, name, name, url, + description, now); + return bs; + } + + private String getJenkinsUrl(Repository repo, JobTemplate jt, + long buildNumber) throws SQLException { + RepositoryConfiguration rc = configurationPersistanceManager + .getRepositoryConfigurationForRepository(repo); + JenkinsServerConfiguration jsc = configurationPersistanceManager + .getJenkinsServerConfiguration(rc.getJenkinsServerName()); + String key = jt.getBuildNameFor(repo); + String url = jsc.getUrlForRepo(repo) + "/job/" + key + "/" + + Long.toString(buildNumber); + return url; + } + + private static String bsToString(BuildStatus bs) { + StringBuffer sb = new StringBuffer(); + sb.append("[BuildStatus "); + sb.append("Name:'" + bs.getKey() + "' "); + sb.append("Type:'" + bs.getName() + "' "); + sb.append("State:'" + bs.getState().toString() + "']"); + return sb.toString(); + } } diff --git a/src/main/resources/static/jenkins-configuration-panel.soy b/src/main/resources/static/jenkins-configuration-panel.soy index b1602e3..fedeb42 100644 --- a/src/main/resources/static/jenkins-configuration-panel.soy +++ b/src/main/resources/static/jenkins-configuration-panel.soy @@ -100,6 +100,12 @@ {param value: ($jenkinsConfig.maxVerifyChain == 0 ? '0' : $jenkinsConfig.maxVerifyChain) /} {param descriptionText: stash_i18n('stash.web.stash.maxVerifyChain.description', 'Maximum number of commits to verify on a single push') /} {/call} + {call aui.form.textField} + {param id: 'prefixTemplate' /} + {param labelContent: stash_i18n('stash.web.stash.prefixTemplate.label', 'Template used to put jobs in separate folders in jenkins') /} + {param value: $jenkinsConfig.prefixTemplate /} + {param descriptionText: stash_i18n('stash.web.stash.prefixTemplate.description', 'Template used to put jobs in separate folders in jenkins. Valid variables are $project and $repo') /} + {/call} {call aui.form.checkboxField} {param legendContent: stash_i18n('stash.web.stash.locked.button.description', 'Lock projects into this configuration') /} {param fields: [[ @@ -177,6 +183,12 @@ {param value: '10' /} {param descriptionText: stash_i18n('stash.web.stash.maxVerifyChain.description', 'Maximum number of commits to verify on a single push') /} {/call} + {call aui.form.textField} + {param id: 'prefixTemplate' /} + {param labelContent: stash_i18n('stash.web.stash.prefixTemplate.label', 'Template used to put jobs in separate folders in jenkins') /} + {param value: '' /} + {param descriptionText: stash_i18n('stash.web.stash.prefixTemplate.description', 'Template used to put jobs in separate folders in jenkins. Valid variables are $project and $repo') /} + {/call} {call aui.form.checkboxField} {param legendContent: stash_i18n('stash.web.stash.locked.button.description', 'Lock projects into this configuration') /} {param fields: [[ diff --git a/src/test/java/com/palantir/stash/stashbot/config/ConfigurationTest.java b/src/test/java/com/palantir/stash/stashbot/config/ConfigurationTest.java index 4c113ca..d3eac23 100644 --- a/src/test/java/com/palantir/stash/stashbot/config/ConfigurationTest.java +++ b/src/test/java/com/palantir/stash/stashbot/config/ConfigurationTest.java @@ -290,6 +290,7 @@ public void testFixesUrlEndingInSlash() throws Exception { new DBParam("PASSWORD", "somepw"), new DBParam("STASH_USERNAME", "someuser"), new DBParam("STASH_PASSWORD", "somepw"), + new DBParam("PREFIX_TEMPLATE", ""), new DBParam("MAX_VERIFY_CHAIN", 1)); JenkinsServerConfiguration jsc = cpm .getJenkinsServerConfiguration("sometest"); diff --git a/src/test/java/com/palantir/stash/stashbot/managers/JenkinsClientManagerTest.java b/src/test/java/com/palantir/stash/stashbot/managers/JenkinsClientManagerTest.java index 8788a14..ed49218 100644 --- a/src/test/java/com/palantir/stash/stashbot/managers/JenkinsClientManagerTest.java +++ b/src/test/java/com/palantir/stash/stashbot/managers/JenkinsClientManagerTest.java @@ -13,6 +13,8 @@ // limitations under the License. package com.palantir.stash.stashbot.managers; +import static org.mockito.Matchers.any; + import java.net.URISyntaxException; import org.junit.Before; @@ -22,37 +24,40 @@ import org.mockito.MockitoAnnotations; import org.springframework.util.Assert; +import com.atlassian.stash.repository.Repository; import com.offbytwo.jenkins.JenkinsServer; -import com.palantir.stash.stashbot.managers.JenkinsClientManager; import com.palantir.stash.stashbot.persistence.JenkinsServerConfiguration; import com.palantir.stash.stashbot.persistence.RepositoryConfiguration; public class JenkinsClientManagerTest { - private static final String JENKINS_URL = "http://www.example.com:8080/jenkins"; - private static final String JENKINS_USERNAME = "jenkins_user"; - private static final String JENKINS_PW = "jenkins_pw"; - @Mock - private RepositoryConfiguration rc; - @Mock - private JenkinsServerConfiguration jsc; - - private JenkinsClientManager jcm; - - @Before - public void setUp() { - - MockitoAnnotations.initMocks(this); - - Mockito.when(jsc.getUrl()).thenReturn(JENKINS_URL); - Mockito.when(jsc.getUsername()).thenReturn(JENKINS_USERNAME); - Mockito.when(jsc.getPassword()).thenReturn(JENKINS_PW); - jcm = new JenkinsClientManager(); - } - - @Test - public void testJCM() throws URISyntaxException { - JenkinsServer js = jcm.getJenkinsServer(jsc, rc); - Assert.notNull(js); - } + private static final String JENKINS_URL = "http://www.example.com:8080/jenkins"; + private static final String JENKINS_USERNAME = "jenkins_user"; + private static final String JENKINS_PW = "jenkins_pw"; + @Mock + private RepositoryConfiguration rc; + @Mock + private JenkinsServerConfiguration jsc; + + private JenkinsClientManager jcm; + + @Before + public void setUp() { + + MockitoAnnotations.initMocks(this); + + Mockito.when(jsc.getUrl()).thenReturn(JENKINS_URL); + Mockito.when(jsc.getUsername()).thenReturn(JENKINS_USERNAME); + Mockito.when(jsc.getPassword()).thenReturn(JENKINS_PW); + Mockito.when(jsc.getPrefixTemplate()).thenReturn(""); + Mockito.when(jsc.getUrlForRepo(any(Repository.class))).thenReturn( + JENKINS_URL); + jcm = new JenkinsClientManager(); + } + + @Test + public void testJCM() throws URISyntaxException { + JenkinsServer js = jcm.getJenkinsServer(jsc, rc, null); + Assert.notNull(js); + } } diff --git a/src/test/java/com/palantir/stash/stashbot/managers/JenkinsManagerTest.java b/src/test/java/com/palantir/stash/stashbot/managers/JenkinsManagerTest.java index 1f78a4c..134a004 100644 --- a/src/test/java/com/palantir/stash/stashbot/managers/JenkinsManagerTest.java +++ b/src/test/java/com/palantir/stash/stashbot/managers/JenkinsManagerTest.java @@ -51,224 +51,231 @@ public class JenkinsManagerTest { - private static final String XML_STRING = ""; - - @Mock - private RepositoryService repositoryService; - @Mock - private ConfigurationPersistenceService cpm; - @Mock - private JenkinsJobXmlFormatter xmlFormatter; - @Mock - private JenkinsClientManager jenkinsClientManager; - @Mock - private JobTemplateManager jtm; - @Mock - private StashbotUrlBuilder sub; - @Mock - private UserService us; - @Mock - private UserManager um; + private static final String XML_STRING = ""; + + @Mock + private RepositoryService repositoryService; + @Mock + private ConfigurationPersistenceService cpm; + @Mock + private JenkinsJobXmlFormatter xmlFormatter; + @Mock + private JenkinsClientManager jenkinsClientManager; + @Mock + private JobTemplateManager jtm; + @Mock + private StashbotUrlBuilder sub; + @Mock + private UserService us; + @Mock + private UserManager um; - private JenkinsManager jenkinsManager; + private JenkinsManager jenkinsManager; - @Mock - private JenkinsServer jenkinsServer; - @Mock - private Repository repo; - @Mock - private Project proj; + @Mock + private JenkinsServer jenkinsServer; + @Mock + private Repository repo; + @Mock + private Project proj; - @Mock - private RepositoryConfiguration rc; - @Mock - private JenkinsServerConfiguration jsc; - @Mock - private UserProfile up; - @Mock - private StashUser su; + @Mock + private RepositoryConfiguration rc; + @Mock + private JenkinsServerConfiguration jsc; + @Mock + private UserProfile up; + @Mock + private StashUser su; - private SecurityService ss; + private SecurityService ss; - private final PluginLoggerFactory lf = new PluginLoggerFactory(); + private final PluginLoggerFactory lf = new PluginLoggerFactory(); - private MockJobTemplateFactory jtf; - private MockSecurityServiceBuilder mssb; + private MockJobTemplateFactory jtf; + private MockSecurityServiceBuilder mssb; + + @Before + public void setUp() throws Throwable { - @Before - public void setUp() throws Throwable { + MockitoAnnotations.initMocks(this); - MockitoAnnotations.initMocks(this); + Mockito.when( + jenkinsClientManager.getJenkinsServer( + Mockito.any(JenkinsServerConfiguration.class), + Mockito.any(RepositoryConfiguration.class), + Mockito.any(Repository.class))).thenReturn( + jenkinsServer); - Mockito.when( - jenkinsClientManager.getJenkinsServer( - Mockito.any(JenkinsServerConfiguration.class), - Mockito.any(RepositoryConfiguration.class))) - .thenReturn(jenkinsServer); + jtf = new MockJobTemplateFactory(jtm); + jtf.generateDefaultsForRepo(repo, rc); - jtf = new MockJobTemplateFactory(jtm); - jtf.generateDefaultsForRepo(repo, rc); + Mockito.when( + xmlFormatter.generateJobXml(Mockito.any(JobTemplate.class), + Mockito.eq(repo))).thenReturn(XML_STRING); - Mockito.when( - xmlFormatter.generateJobXml(Mockito.any(JobTemplate.class), - Mockito.eq(repo))).thenReturn(XML_STRING); + Mockito.when(cpm.getJenkinsServerConfiguration(Mockito.anyString())) + .thenReturn(jsc); + Mockito.when(cpm.getRepositoryConfigurationForRepository(repo)) + .thenReturn(rc); + Mockito.when(jsc.getStashUsername()).thenReturn("stash_username"); + Mockito.when(jsc.getStashPassword()).thenReturn("stash_password"); + Mockito.when(jsc.getPassword()).thenReturn("jenkins_password"); - Mockito.when(cpm.getJenkinsServerConfiguration(Mockito.anyString())) - .thenReturn(jsc); - Mockito.when(cpm.getRepositoryConfigurationForRepository(repo)) - .thenReturn(rc); - Mockito.when(jsc.getStashUsername()).thenReturn("stash_username"); - Mockito.when(jsc.getStashPassword()).thenReturn("stash_password"); - Mockito.when(jsc.getPassword()).thenReturn("jenkins_password"); + Mockito.when(repo.getName()).thenReturn("somename"); + Mockito.when(repo.getSlug()).thenReturn("slug"); + Mockito.when(repo.getProject()).thenReturn(proj); + Mockito.when(proj.getKey()).thenReturn("project_key"); - Mockito.when(repo.getName()).thenReturn("somename"); - Mockito.when(repo.getSlug()).thenReturn("slug"); - Mockito.when(repo.getProject()).thenReturn(proj); - Mockito.when(proj.getKey()).thenReturn("project_key"); + Mockito.when(um.getRemoteUser()).thenReturn(up); + Mockito.when(up.getUsername()).thenReturn("someuser"); + Mockito.when(us.getUserByName(Mockito.anyString())).thenReturn(su); - Mockito.when(um.getRemoteUser()).thenReturn(up); - Mockito.when(up.getUsername()).thenReturn("someuser"); - Mockito.when(us.getUserByName(Mockito.anyString())).thenReturn(su); + mssb = new MockSecurityServiceBuilder(); - mssb = new MockSecurityServiceBuilder(); + ss = mssb.getSecurityService(); - ss = mssb.getSecurityService(); + jenkinsManager = new JenkinsManager(repositoryService, cpm, jtm, + xmlFormatter, jenkinsClientManager, sub, lf, ss, us, um); + } - jenkinsManager = new JenkinsManager(repositoryService, cpm, jtm, - xmlFormatter, jenkinsClientManager, sub, lf, ss, us, um); - } + @Test + public void testCreateJob() throws Exception { - @Test - public void testCreateJob() throws Exception { + JobTemplate jt = jtm.getDefaultVerifyJob(); - JobTemplate jt = jtm.getDefaultVerifyJob(); + jenkinsManager.createJob(repo, jt); - jenkinsManager.createJob(repo, jt); + ArgumentCaptor xmlCaptor = ArgumentCaptor + .forClass(String.class); - ArgumentCaptor xmlCaptor = ArgumentCaptor - .forClass(String.class); + Mockito.verify(xmlFormatter).generateJobXml(jt, repo); + Mockito.verify(jenkinsServer).createJob(Mockito.anyString(), + xmlCaptor.capture()); - Mockito.verify(xmlFormatter).generateJobXml(jt, repo); - Mockito.verify(jenkinsServer).createJob(Mockito.anyString(), - xmlCaptor.capture()); + Assert.assertEquals(XML_STRING, xmlCaptor.getValue()); + } - Assert.assertEquals(XML_STRING, xmlCaptor.getValue()); - } + @Test + public void testUpdateJob() throws Exception { - @Test - public void testUpdateJob() throws Exception { + String jobName = "somename_verification"; - String jobName = "somename_verification"; + Job existingJob = Mockito.mock(Job.class); + Map jobMap = new HashMap(); + jobMap.put(jobName, existingJob); + Mockito.when(jenkinsServer.getJobs()).thenReturn(jobMap); - Job existingJob = Mockito.mock(Job.class); - Map jobMap = new HashMap(); - jobMap.put(jobName, existingJob); - Mockito.when(jenkinsServer.getJobs()).thenReturn(jobMap); + JobTemplate jt = jtm.getDefaultVerifyJob(); - JobTemplate jt = jtm.getDefaultVerifyJob(); + jenkinsManager.updateJob(repo, jt); - jenkinsManager.updateJob(repo, jt); + ArgumentCaptor xmlCaptor = ArgumentCaptor + .forClass(String.class); - ArgumentCaptor xmlCaptor = ArgumentCaptor - .forClass(String.class); + Mockito.verify(xmlFormatter).generateJobXml(jt, repo); + Mockito.verify(jenkinsServer).updateJob(Mockito.anyString(), + xmlCaptor.capture()); + Mockito.verify(jenkinsServer, Mockito.never()).createJob( + Mockito.anyString(), Mockito.anyString()); - Mockito.verify(xmlFormatter).generateJobXml(jt, repo); - Mockito.verify(jenkinsServer).updateJob(Mockito.anyString(), - xmlCaptor.capture()); - Mockito.verify(jenkinsServer, Mockito.never()).createJob( - Mockito.anyString(), Mockito.anyString()); + Assert.assertEquals(XML_STRING, xmlCaptor.getValue()); + } - Assert.assertEquals(XML_STRING, xmlCaptor.getValue()); - } + @Test + public void testTriggerBuildShort() throws Exception { + String HASH = "38356e8abe0e96538dd1007278ecc02c3bf3d2cb"; + String REF = "refs/heads/master"; - @Test - public void testTriggerBuildShort() throws Exception { - String HASH = "38356e8abe0e96538dd1007278ecc02c3bf3d2cb"; - String REF = "refs/heads/master"; + JobTemplate jt = jtm.getDefaultVerifyJob(); - JobTemplate jt = jtm.getDefaultVerifyJob(); + String jobName = jt.getBuildNameFor(repo); + Job existingJob = Mockito.mock(Job.class); + Mockito.when(existingJob.getName()).thenReturn(jobName); + Map jobMap = new HashMap(); + jobMap.put(jobName, existingJob); + Mockito.when(jenkinsServer.getJobs()).thenReturn(jobMap); - String jobName = jt.getBuildNameFor(repo); - Job existingJob = Mockito.mock(Job.class); - Mockito.when(existingJob.getName()).thenReturn(jobName); - Map jobMap = new HashMap(); - jobMap.put(jobName, existingJob); - Mockito.when(jenkinsServer.getJobs()).thenReturn(jobMap); + Mockito.when(jtm.getJobTemplate(JobType.VERIFY_COMMIT, rc)).thenReturn( + jt); - Mockito.when(jtm.getJobTemplate(JobType.VERIFY_COMMIT, rc)).thenReturn( - jt); + jenkinsManager.triggerBuild(repo, JobType.VERIFY_COMMIT, HASH, REF); + jenkinsManager.destroy(); - jenkinsManager.triggerBuild(repo, JobType.VERIFY_COMMIT, HASH, REF); - jenkinsManager.destroy(); + @SuppressWarnings({ "unchecked", "rawtypes" }) + Class> forClass = (Class) Map.class; + ArgumentCaptor> paramCaptor = ArgumentCaptor + .forClass(forClass); - @SuppressWarnings({ "unchecked", "rawtypes" }) - Class> forClass = (Class) Map.class; - ArgumentCaptor> paramCaptor = ArgumentCaptor - .forClass(forClass); + Mockito.verify(existingJob).build(paramCaptor.capture()); - Mockito.verify(existingJob).build(paramCaptor.capture()); + Map paramMap = paramCaptor.getValue(); + Assert.assertTrue(paramMap.containsKey("buildHead")); + Assert.assertTrue(paramMap.containsKey("buildRef")); + Assert.assertTrue(paramMap.containsKey("repoId")); + Assert.assertFalse(paramMap.containsKey("pullRequestId")); + Assert.assertFalse(paramMap.containsKey("mergeHead")); + } - Map paramMap = paramCaptor.getValue(); - Assert.assertTrue(paramMap.containsKey("buildHead")); - Assert.assertTrue(paramMap.containsKey("buildRef")); - Assert.assertTrue(paramMap.containsKey("repoId")); - Assert.assertFalse(paramMap.containsKey("pullRequestId")); - Assert.assertFalse(paramMap.containsKey("mergeHead")); - } + @Test + public void testUpdateRepoCIEnabled() throws IOException { - @Test - public void testUpdateRepoCIEnabled() throws IOException { + Mockito.when(rc.getCiEnabled()).thenReturn(true); - Mockito.when(rc.getCiEnabled()).thenReturn(true); + jenkinsManager.updateRepo(repo); - jenkinsManager.updateRepo(repo); + List templates = jtf.getMockTemplates(); - List templates = jtf.getMockTemplates(); + for (JobTemplate t : templates) { + Mockito.verify(jenkinsServer).createJob( + Mockito.eq(t.getBuildNameFor(repo)), Mockito.anyString()); + } + } - for (JobTemplate t : templates) { - Mockito.verify(jenkinsServer).createJob( - Mockito.eq(t.getBuildNameFor(repo)), Mockito.anyString()); - } - } + @Test + public void testUpdateRepoCIDisabled() throws IOException { - @Test - public void testUpdateRepoCIDisabled() throws IOException { + Mockito.when(rc.getCiEnabled()).thenReturn(false); - Mockito.when(rc.getCiEnabled()).thenReturn(false); + jenkinsManager.updateRepo(repo); - jenkinsManager.updateRepo(repo); + Mockito.verify(jenkinsServer, Mockito.never()).createJob( + Mockito.anyString(), Mockito.anyString()); + } - Mockito.verify(jenkinsServer, Mockito.never()).createJob( - Mockito.anyString(), Mockito.anyString()); - } + @Test + public void testPreserveJenkinsJobConfigDisabled() throws IOException { - @Test - public void testPreserveJenkinsJobConfigDisabled() throws IOException { + JobTemplate jt = jtm.getDefaultVerifyJob(); + HashMap jobs = Maps.newHashMap(); + jobs.put(jt.getBuildNameFor(repo), new Job()); // update job logic + // requires the job be + // there already - JobTemplate jt = jtm.getDefaultVerifyJob(); - HashMap jobs = Maps.newHashMap(); - jobs.put(jt.getBuildNameFor(repo), new Job()); // update job logic requires the job be there already + Mockito.when(rc.getPreserveJenkinsJobConfig()).thenReturn(false); + Mockito.when(jenkinsServer.getJobs()).thenReturn(jobs); - Mockito.when(rc.getPreserveJenkinsJobConfig()).thenReturn(false); - Mockito.when(jenkinsServer.getJobs()).thenReturn(jobs); + jenkinsManager.updateJob(repo, jt); - jenkinsManager.updateJob(repo, jt); + Mockito.verify(jenkinsServer).updateJob(Mockito.anyString(), + Mockito.anyString()); + } - Mockito.verify(jenkinsServer).updateJob(Mockito.anyString(), Mockito.anyString()); - } + @Test + public void testPreserveJenkinsJobConfigEnabled() throws IOException { - @Test - public void testPreserveJenkinsJobConfigEnabled() throws IOException { + JobTemplate jt = jtm.getDefaultVerifyJob(); + HashMap jobs = Maps.newHashMap(); + jobs.put(jt.getBuildNameFor(repo), new Job()); // update job logic + // requires the job be + // there already - JobTemplate jt = jtm.getDefaultVerifyJob(); - HashMap jobs = Maps.newHashMap(); - jobs.put(jt.getBuildNameFor(repo), new Job()); // update job logic requires the job be there already + Mockito.when(rc.getPreserveJenkinsJobConfig()).thenReturn(true); + Mockito.when(jenkinsServer.getJobs()).thenReturn(jobs); - Mockito.when(rc.getPreserveJenkinsJobConfig()).thenReturn(true); - Mockito.when(jenkinsServer.getJobs()).thenReturn(jobs); + jenkinsManager.updateJob(repo, jt); - jenkinsManager.updateJob(repo, jt); - - Mockito.verify(jenkinsServer, Mockito.never()).updateJob(Mockito.anyString(), Mockito.anyString()); - } + Mockito.verify(jenkinsServer, Mockito.never()).updateJob( + Mockito.anyString(), Mockito.anyString()); + } } From b9fdea3ebeaa7b1506030f8a9133239f1aaf8c85 Mon Sep 17 00:00:00 2001 From: Andrew Karnani Date: Fri, 9 Oct 2015 16:41:18 -0700 Subject: [PATCH 04/54] Adds support for templating job names. Need to add a migration to set the default for existing servers. Though currently we're not planning on supporting upgrading from other versions to this one. --- pom.xml | 2 +- .../config/ConfigurationPersistenceImpl.java | 10 ++++++---- .../config/ConfigurationPersistenceService.java | 2 +- .../stashbot/jobtemplate/JobTemplateImpl.java | 7 +++++-- .../stashbot/managers/JenkinsClientManager.java | 2 +- .../stash/stashbot/managers/JenkinsManager.java | 16 ++++++++-------- .../persistence/JenkinsServerConfiguration.java | 10 ++++++---- .../stash/stashbot/persistence/JobTemplate.java | 2 +- .../servlet/BuildSuccessReportingServlet.java | 9 +++++++-- .../static/jenkins-configuration-panel.soy | 12 ++++++++++++ .../stashbot/managers/JenkinsManagerTest.java | 8 ++++---- .../stashbot/mocks/MockJobTemplateFactory.java | 8 +++++++- 12 files changed, 59 insertions(+), 29 deletions(-) diff --git a/pom.xml b/pom.xml index 902068e..c62403f 100644 --- a/pom.xml +++ b/pom.xml @@ -127,7 +127,7 @@ com.offbytwo.jenkins jenkins-client - 0.1.5 + 0.3.0 diff --git a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java index 935d670..4583a2f 100644 --- a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java +++ b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java @@ -132,12 +132,13 @@ public void setJenkinsServerConfigurationFromRequest(HttpServletRequest req) Integer maxVerifyChain = Integer.parseInt(req .getParameter("maxVerifyChain")); String prefixTemplate = req.getParameter("prefixTemplate"); + String jobTemplate = req.getParameter("jobTemplate"); String lockStr = req.getParameter("locked"); Boolean isLocked = (lockStr == null || !lockStr.equals("on")) ? false : true; setJenkinsServerConfiguration(name, url, username, password, am, - stashUsername, stashPassword, maxVerifyChain, prefixTemplate, + stashUsername, stashPassword, maxVerifyChain, prefixTemplate, jobTemplate, isLocked); } @@ -167,7 +168,7 @@ public void setJenkinsServerConfiguration(String name, String url, throws SQLException { setJenkinsServerConfiguration(name, url, username, password, authenticationMode, stashUsername, stashPassword, - maxVerifyChain, "", false); + maxVerifyChain, "", "$project_$repo", false); } /* @@ -185,7 +186,7 @@ public void setJenkinsServerConfiguration(String name, String url, String username, String password, AuthenticationMode authenticationMode, String stashUsername, String stashPassword, Integer maxVerifyChain, - String prefixTemplate, Boolean isLocked) throws SQLException { + String prefixTemplate, String jobTemplate, Boolean isLocked) throws SQLException { if (name == null) { name = DEFAULT_JENKINS_SERVER_CONFIG_KEY; } @@ -202,7 +203,7 @@ public void setJenkinsServerConfiguration(String name, String url, "STASH_USERNAME", stashUsername), new DBParam( "STASH_PASSWORD", stashPassword), new DBParam( "MAX_VERIFY_CHAIN", maxVerifyChain), new DBParam( - "PREFIX_TEMPLATE", prefixTemplate), new DBParam("LOCKED", + "PREFIX_TEMPLATE", prefixTemplate), new DBParam("JOB_TEMPLATE", jobTemplate), new DBParam("LOCKED", isLocked)); return; } @@ -216,6 +217,7 @@ public void setJenkinsServerConfiguration(String name, String url, configs[0].setStashPassword(stashPassword); configs[0].setMaxVerifyChain(maxVerifyChain); configs[0].setPrefixTemplate(prefixTemplate); + configs[0].setJobTemplate(jobTemplate); configs[0].setLocked(isLocked); configs[0].save(); } diff --git a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java index 65158dd..1ef1753 100644 --- a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java +++ b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java @@ -60,7 +60,7 @@ public abstract void setJenkinsServerConfiguration(String name, String url, String username, String password, AuthenticationMode authenticationMode, String stashUsername, String stashPassword, Integer maxVerifyChain, - String prefixTemplate, Boolean isLocked) throws SQLException; + String prefixTemplate, String jobTemplate, Boolean isLocked) throws SQLException; public abstract RepositoryConfiguration getRepositoryConfigurationForRepository( Repository repo) throws SQLException; diff --git a/src/main/java/com/palantir/stash/stashbot/jobtemplate/JobTemplateImpl.java b/src/main/java/com/palantir/stash/stashbot/jobtemplate/JobTemplateImpl.java index 5f7ef79..2016a5d 100644 --- a/src/main/java/com/palantir/stash/stashbot/jobtemplate/JobTemplateImpl.java +++ b/src/main/java/com/palantir/stash/stashbot/jobtemplate/JobTemplateImpl.java @@ -14,6 +14,7 @@ package com.palantir.stash.stashbot.jobtemplate; import com.atlassian.stash.repository.Repository; +import com.palantir.stash.stashbot.persistence.JenkinsServerConfiguration; import com.palantir.stash.stashbot.persistence.JobTemplate; // Custom AO implementation @@ -27,10 +28,12 @@ public JobTemplateImpl(JobTemplate jt) { } // TODO: remove invalid characters from repo - public String getBuildNameFor(Repository repo) { + public String getBuildNameFor(Repository repo, JenkinsServerConfiguration jsc) { String project = repo.getProject().getKey(); String nameSlug = repo.getSlug(); - String key = project + "_" + nameSlug + "_" + dis.getJobType().toString(); + String template = jsc.getJobTemplate(); + template = template.replaceAll("\\$repo", nameSlug).replaceAll("\\$project", project); + String key = template + "_" + dis.getJobType().toString(); // jenkins does toLowerCase() on all keys, so we must do the same return key.toLowerCase(); } diff --git a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsClientManager.java b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsClientManager.java index bee6f58..b269d1b 100644 --- a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsClientManager.java +++ b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsClientManager.java @@ -34,7 +34,7 @@ public class JenkinsClientManager { public JenkinsServer getJenkinsServer(JenkinsServerConfiguration jsc, RepositoryConfiguration rc, Repository r) throws URISyntaxException { - + System.out.println(jsc.getUrlForRepo(r)); return new JenkinsServer(new URI(jsc.getUrlForRepo(r)), jsc.getUsername(), jsc.getPassword()); } diff --git a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java index c5fdffa..6f6f145 100644 --- a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java +++ b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java @@ -109,7 +109,7 @@ public void createJob(Repository repo, JobTemplate jobTemplate) { .getJenkinsServerConfiguration(rc.getJenkinsServerName()); final JenkinsServer jenkinsServer = jenkinsClientManager .getJenkinsServer(jsc, rc, repo); - final String jobName = jobTemplate.getBuildNameFor(repo); + final String jobName = jobTemplate.getBuildNameFor(repo, jsc); // If we try to create a job which already exists, we still get a // 200... so we should check first to make @@ -151,7 +151,7 @@ public void updateJob(Repository repo, JobTemplate jobTemplate) { .getJenkinsServerConfiguration(rc.getJenkinsServerName()); final JenkinsServer jenkinsServer = jenkinsClientManager .getJenkinsServer(jsc, rc, repo); - final String jobName = jobTemplate.getBuildNameFor(repo); + final String jobName = jobTemplate.getBuildNameFor(repo, jsc); // If we try to create a job which already exists, we still get a // 200... so we should check first to make @@ -250,7 +250,7 @@ public void synchronousTriggerBuild(Repository repo, JobType jobType, .getJenkinsServerConfiguration(rc.getJenkinsServerName()); JobTemplate jt = jtm.getJobTemplate(jobType, rc); - String jenkinsBuildId = jt.getBuildNameFor(repo); + String jenkinsBuildId = jt.getBuildNameFor(repo, jsc); String url = jsc.getUrlForRepo(repo); String user = jsc.getUsername(); String password = jsc.getPassword(); @@ -262,7 +262,7 @@ public void synchronousTriggerBuild(Repository repo, JobType jobType, final JenkinsServer js = jenkinsClientManager.getJenkinsServer(jsc, rc, repo); Map jobMap = js.getJobs(); - String key = jt.getBuildNameFor(repo); + String key = jt.getBuildNameFor(repo, jsc); if (!jobMap.containsKey(key)) { throw new RuntimeException("Build doesn't exist: " + key); @@ -312,7 +312,7 @@ public void synchronousTriggerBuild(Repository repo, JobType jobType, .getJenkinsServerConfiguration(rc.getJenkinsServerName()); JobTemplate jt = jtm.getJobTemplate(jobType, rc); - String jenkinsBuildId = jt.getBuildNameFor(repo); + String jenkinsBuildId = jt.getBuildNameFor(repo, jsc); String url = jsc.getUrlForRepo(repo); String user = jsc.getUsername(); String password = jsc.getPassword(); @@ -324,7 +324,7 @@ public void synchronousTriggerBuild(Repository repo, JobType jobType, final JenkinsServer js = jenkinsClientManager.getJenkinsServer(jsc, rc, repo); Map jobMap = js.getJobs(); - String key = jt.getBuildNameFor(repo); + String key = jt.getBuildNameFor(repo, jsc); if (!jobMap.containsKey(key)) { throw new RuntimeException("Build doesn't exist: " + key); @@ -410,7 +410,7 @@ public Void call() throws Exception { Map jobs = js.getJobs(); for (JobTemplate template : templates) { - if (!jobs.containsKey(template.getBuildNameFor(r))) { + if (!jobs.containsKey(template.getBuildNameFor(r, jsc))) { log.info("Creating " + template.getName() + " job for repo " + r.toString()); createJob(r, template); @@ -490,7 +490,7 @@ public Void call() throws Exception { JenkinsServer js = jcm.getJenkinsServer(jsc, rc, this.r); Map jobs = js.getJobs(); for (JobTemplate jobTemplate : templates) { - if (!jobs.containsKey(jobTemplate.getBuildNameFor(r))) { + if (!jobs.containsKey(jobTemplate.getBuildNameFor(r, jsc))) { log.info("Creating " + jobTemplate.getName() + " job for repo " + r.toString()); createJob(r, jobTemplate); diff --git a/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java b/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java index 1369463..816639f 100644 --- a/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java +++ b/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java @@ -186,15 +186,17 @@ public static ImmutableList> getSelectList( public void setPrefixTemplate(String template); + @NotNull + @Default("$project_$repo") + public String getJobTemplate(); + + public void setJobTemplate(String template); + // Implemented in JenkinsServerConfigurationImpl - expands variables in // template and appends to url. @Ignore public String getUrlForRepo(Repository r); - @Ignore - @Deprecated - public void setUrlForRepo(String s); - // For security - allow a jenkins server config to be locked to // non-system-admins @NotNull diff --git a/src/main/java/com/palantir/stash/stashbot/persistence/JobTemplate.java b/src/main/java/com/palantir/stash/stashbot/persistence/JobTemplate.java index d29177e..d4e6554 100644 --- a/src/main/java/com/palantir/stash/stashbot/persistence/JobTemplate.java +++ b/src/main/java/com/palantir/stash/stashbot/persistence/JobTemplate.java @@ -49,5 +49,5 @@ public interface JobTemplate extends Entity { // Implemented logic outside of CRUD @Ignore - public String getBuildNameFor(Repository repo); + public String getBuildNameFor(Repository repo, JenkinsServerConfiguration jsc); } diff --git a/src/main/java/com/palantir/stash/stashbot/servlet/BuildSuccessReportingServlet.java b/src/main/java/com/palantir/stash/stashbot/servlet/BuildSuccessReportingServlet.java index 6e0e1cc..f424e8c 100644 --- a/src/main/java/com/palantir/stash/stashbot/servlet/BuildSuccessReportingServlet.java +++ b/src/main/java/com/palantir/stash/stashbot/servlet/BuildSuccessReportingServlet.java @@ -330,11 +330,16 @@ private void printOutput(HttpServletRequest req, HttpServletResponse res) private BuildStatus getSuccessStatus(Repository repo, JobTemplate jt, State state, long buildNumber, String buildHead) throws SQLException { + RepositoryConfiguration rc = configurationPersistanceManager + .getRepositoryConfigurationForRepository(repo); + JenkinsServerConfiguration jsc = configurationPersistanceManager + .getJenkinsServerConfiguration(rc.getJenkinsServerName()); + Date now = new Date(java.lang.System.currentTimeMillis()); DateFormat df = DateFormat.getDateInstance(); // key will be the jenkins name - String key = jt.getBuildNameFor(repo); + String key = jt.getBuildNameFor(repo, jsc); String name = key + " (build " + Long.toString(buildNumber) + ")"; String description = "Build " + Long.toString(buildNumber) + " " + state.toString() + " at " + df.format(now); @@ -350,7 +355,7 @@ private String getJenkinsUrl(Repository repo, JobTemplate jt, .getRepositoryConfigurationForRepository(repo); JenkinsServerConfiguration jsc = configurationPersistanceManager .getJenkinsServerConfiguration(rc.getJenkinsServerName()); - String key = jt.getBuildNameFor(repo); + String key = jt.getBuildNameFor(repo, jsc); String url = jsc.getUrlForRepo(repo) + "/job/" + key + "/" + Long.toString(buildNumber); return url; diff --git a/src/main/resources/static/jenkins-configuration-panel.soy b/src/main/resources/static/jenkins-configuration-panel.soy index fedeb42..3c29c67 100644 --- a/src/main/resources/static/jenkins-configuration-panel.soy +++ b/src/main/resources/static/jenkins-configuration-panel.soy @@ -106,6 +106,12 @@ {param value: $jenkinsConfig.prefixTemplate /} {param descriptionText: stash_i18n('stash.web.stash.prefixTemplate.description', 'Template used to put jobs in separate folders in jenkins. Valid variables are $project and $repo') /} {/call} + {call aui.form.textField} + {param id: 'jobTemplate' /} + {param labelContent: stash_i18n('stash.web.stash.jobTemplate.label', 'Template used to generate job names') /} + {param value: $jenkinsConfig.jobTemplate /} + {param descriptionText: stash_i18n('stash.web.stash.jobTemplate.description', 'Valid variables are $project and $repo. Template will have the job type appended') /} + {/call} {call aui.form.checkboxField} {param legendContent: stash_i18n('stash.web.stash.locked.button.description', 'Lock projects into this configuration') /} {param fields: [[ @@ -189,6 +195,12 @@ {param value: '' /} {param descriptionText: stash_i18n('stash.web.stash.prefixTemplate.description', 'Template used to put jobs in separate folders in jenkins. Valid variables are $project and $repo') /} {/call} + {call aui.form.textField} + {param id: 'jobTemplate' /} + {param labelContent: stash_i18n('stash.web.stash.jobTemplate.label', 'Template used to generate job names') /} + {param value: '$project_$repo' /} + {param descriptionText: stash_i18n('stash.web.stash.jobTemplate.description', 'Valid variables are $project and $repo. Template will have the job type appended') /} + {/call} {call aui.form.checkboxField} {param legendContent: stash_i18n('stash.web.stash.locked.button.description', 'Lock projects into this configuration') /} {param fields: [[ diff --git a/src/test/java/com/palantir/stash/stashbot/managers/JenkinsManagerTest.java b/src/test/java/com/palantir/stash/stashbot/managers/JenkinsManagerTest.java index 134a004..10b67ca 100644 --- a/src/test/java/com/palantir/stash/stashbot/managers/JenkinsManagerTest.java +++ b/src/test/java/com/palantir/stash/stashbot/managers/JenkinsManagerTest.java @@ -189,7 +189,7 @@ public void testTriggerBuildShort() throws Exception { JobTemplate jt = jtm.getDefaultVerifyJob(); - String jobName = jt.getBuildNameFor(repo); + String jobName = jt.getBuildNameFor(repo, jsc); Job existingJob = Mockito.mock(Job.class); Mockito.when(existingJob.getName()).thenReturn(jobName); Map jobMap = new HashMap(); @@ -228,7 +228,7 @@ public void testUpdateRepoCIEnabled() throws IOException { for (JobTemplate t : templates) { Mockito.verify(jenkinsServer).createJob( - Mockito.eq(t.getBuildNameFor(repo)), Mockito.anyString()); + Mockito.eq(t.getBuildNameFor(repo, jsc)), Mockito.anyString()); } } @@ -248,7 +248,7 @@ public void testPreserveJenkinsJobConfigDisabled() throws IOException { JobTemplate jt = jtm.getDefaultVerifyJob(); HashMap jobs = Maps.newHashMap(); - jobs.put(jt.getBuildNameFor(repo), new Job()); // update job logic + jobs.put(jt.getBuildNameFor(repo, jsc), new Job()); // update job logic // requires the job be // there already @@ -266,7 +266,7 @@ public void testPreserveJenkinsJobConfigEnabled() throws IOException { JobTemplate jt = jtm.getDefaultVerifyJob(); HashMap jobs = Maps.newHashMap(); - jobs.put(jt.getBuildNameFor(repo), new Job()); // update job logic + jobs.put(jt.getBuildNameFor(repo, jsc), new Job()); // update job logic // requires the job be // there already diff --git a/src/test/java/com/palantir/stash/stashbot/mocks/MockJobTemplateFactory.java b/src/test/java/com/palantir/stash/stashbot/mocks/MockJobTemplateFactory.java index 89892d8..814a1bf 100644 --- a/src/test/java/com/palantir/stash/stashbot/mocks/MockJobTemplateFactory.java +++ b/src/test/java/com/palantir/stash/stashbot/mocks/MockJobTemplateFactory.java @@ -13,6 +13,9 @@ // limitations under the License. package com.palantir.stash.stashbot.mocks; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; + import java.util.ArrayList; import java.util.List; @@ -22,6 +25,7 @@ import com.google.common.collect.ImmutableList; import com.palantir.stash.stashbot.jobtemplate.JobTemplateManager; import com.palantir.stash.stashbot.jobtemplate.JobType; +import com.palantir.stash.stashbot.persistence.JenkinsServerConfiguration; import com.palantir.stash.stashbot.persistence.JobMapping; import com.palantir.stash.stashbot.persistence.JobTemplate; import com.palantir.stash.stashbot.persistence.RepositoryConfiguration; @@ -55,7 +59,9 @@ public JobTemplate getJobTemplate(Repository repo, RepositoryConfiguration rc, J Mockito.when(template.getJobType()).thenReturn(jt); Mockito.when(template.getName()).thenReturn(jt.toString()); - Mockito.when(template.getBuildNameFor(repo)).thenReturn("somename_" + jt.toString()); + Mockito.when(template.getBuildNameFor(eq(repo), any(JenkinsServerConfiguration.class))) + .thenReturn( + "somename_" + jt.toString()); Mockito.when(template.getTemplateFile()).thenReturn("src/test/resources/test-template.vm"); JobMapping jm = Mockito.mock(JobMapping.class); From aec6b449ecd45a9cb63c1ddddb99297651b3ba61 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Wed, 14 Oct 2015 18:40:44 -0700 Subject: [PATCH 05/54] Add a default value of / -If things end in /, it'll be stripped off making it non-empty anyways -We can't have empty strings in AO --- .../stash/stashbot/config/ConfigurationPersistenceImpl.java | 2 +- .../stash/stashbot/persistence/JenkinsServerConfiguration.java | 3 ++- .../com/palantir/stash/stashbot/config/ConfigurationTest.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java index 4583a2f..1a67663 100644 --- a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java +++ b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java @@ -168,7 +168,7 @@ public void setJenkinsServerConfiguration(String name, String url, throws SQLException { setJenkinsServerConfiguration(name, url, username, password, authenticationMode, stashUsername, stashPassword, - maxVerifyChain, "", "$project_$repo", false); + maxVerifyChain, "/", "$project_$repo", false); } /* diff --git a/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java b/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java index 816639f..21e1151 100644 --- a/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java +++ b/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java @@ -182,8 +182,9 @@ public static ImmutableList> getSelectList( public void setMaxVerifyChain(Integer max); + @NotNull + @Default("/") public String getPrefixTemplate(); - public void setPrefixTemplate(String template); @NotNull diff --git a/src/test/java/com/palantir/stash/stashbot/config/ConfigurationTest.java b/src/test/java/com/palantir/stash/stashbot/config/ConfigurationTest.java index d3eac23..7207205 100644 --- a/src/test/java/com/palantir/stash/stashbot/config/ConfigurationTest.java +++ b/src/test/java/com/palantir/stash/stashbot/config/ConfigurationTest.java @@ -290,7 +290,7 @@ public void testFixesUrlEndingInSlash() throws Exception { new DBParam("PASSWORD", "somepw"), new DBParam("STASH_USERNAME", "someuser"), new DBParam("STASH_PASSWORD", "somepw"), - new DBParam("PREFIX_TEMPLATE", ""), + new DBParam("PREFIX_TEMPLATE", "/"), new DBParam("MAX_VERIFY_CHAIN", 1)); JenkinsServerConfiguration jsc = cpm .getJenkinsServerConfiguration("sometest"); From e0a1c339d76691a3b7d3f77261b9e524ecf0b88b Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Thu, 15 Oct 2015 21:42:10 -0700 Subject: [PATCH 06/54] Change to use our internal jenkins-client and the idea of prefix --- pom.xml | 2 +- .../stash/stashbot/config/ConfigurationPersistenceImpl.java | 6 ------ .../stash/stashbot/managers/JenkinsClientManager.java | 2 +- .../palantir/stash/stashbot/managers/JenkinsManager.java | 4 ++-- .../persistence/JenkinsServerConfigurationImpl.java | 4 ++-- .../palantir/stash/stashbot/persistence/JobTemplate.java | 2 ++ .../stashbot/servlet/BuildSuccessReportingServlet.java | 6 +++--- 7 files changed, 11 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index c62403f..8d490b0 100644 --- a/pom.xml +++ b/pom.xml @@ -127,7 +127,7 @@ com.offbytwo.jenkins jenkins-client - 0.3.0 + 0.3.0.p1 diff --git a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java index 1a67663..9a7df7c 100644 --- a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java +++ b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java @@ -101,12 +101,6 @@ public JenkinsServerConfiguration getJenkinsServerConfiguration(String name) configs[0].setUrl(url); configs[0].save(); } - String template = configs[0].getPrefixTemplate(); - if (template.endsWith("/")) { - template = template.substring(0, template.length() - 1); - configs[0].setPrefixTemplate(template); - configs[0].save(); - } return configs[0]; } diff --git a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsClientManager.java b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsClientManager.java index b269d1b..91fb132 100644 --- a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsClientManager.java +++ b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsClientManager.java @@ -35,7 +35,7 @@ public class JenkinsClientManager { public JenkinsServer getJenkinsServer(JenkinsServerConfiguration jsc, RepositoryConfiguration rc, Repository r) throws URISyntaxException { System.out.println(jsc.getUrlForRepo(r)); - return new JenkinsServer(new URI(jsc.getUrlForRepo(r)), + return new JenkinsServer(new URI(jsc.getUrl()), jsc.getUrlForRepo(r), jsc.getUsername(), jsc.getPassword()); } diff --git a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java index 6f6f145..224bce1 100644 --- a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java +++ b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java @@ -251,7 +251,7 @@ public void synchronousTriggerBuild(Repository repo, JobType jobType, JobTemplate jt = jtm.getJobTemplate(jobType, rc); String jenkinsBuildId = jt.getBuildNameFor(repo, jsc); - String url = jsc.getUrlForRepo(repo); + String url = jsc.getUrl() + jsc.getUrlForRepo(repo); String user = jsc.getUsername(); String password = jsc.getPassword(); @@ -313,7 +313,7 @@ public void synchronousTriggerBuild(Repository repo, JobType jobType, JobTemplate jt = jtm.getJobTemplate(jobType, rc); String jenkinsBuildId = jt.getBuildNameFor(repo, jsc); - String url = jsc.getUrlForRepo(repo); + String url = jsc.getUrl() + jsc.getUrlForRepo(repo); String user = jsc.getUsername(); String password = jsc.getPassword(); diff --git a/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfigurationImpl.java b/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfigurationImpl.java index 8fd4736..f78373f 100644 --- a/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfigurationImpl.java +++ b/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfigurationImpl.java @@ -39,9 +39,9 @@ public String getUrlForRepo(Repository r) { .getKey()); template = template.replaceAll("\\$repo", r.getSlug()); - return jsc.getUrl() + template; + return template; } else { - return jsc.getUrl(); + return ""; } } } diff --git a/src/main/java/com/palantir/stash/stashbot/persistence/JobTemplate.java b/src/main/java/com/palantir/stash/stashbot/persistence/JobTemplate.java index d4e6554..638218f 100644 --- a/src/main/java/com/palantir/stash/stashbot/persistence/JobTemplate.java +++ b/src/main/java/com/palantir/stash/stashbot/persistence/JobTemplate.java @@ -30,6 +30,8 @@ @Implementation(JobTemplateImpl.class) public interface JobTemplate extends Entity { + + @NotNull @Unique public String getName(); diff --git a/src/main/java/com/palantir/stash/stashbot/servlet/BuildSuccessReportingServlet.java b/src/main/java/com/palantir/stash/stashbot/servlet/BuildSuccessReportingServlet.java index f424e8c..38d8638 100644 --- a/src/main/java/com/palantir/stash/stashbot/servlet/BuildSuccessReportingServlet.java +++ b/src/main/java/com/palantir/stash/stashbot/servlet/BuildSuccessReportingServlet.java @@ -56,7 +56,7 @@ public class BuildSuccessReportingServlet extends HttpServlet { /** * Handle information about build success / failure for a given hash and/or * pull request - * + * * URL is of the form * BASE_URL/REPO_ID/TYPE/STATE/BUILD_NUMBER/BUILD_HEAD[/MERGE_HEAD * /PULLREQUEST_ID]
@@ -68,7 +68,7 @@ public class BuildSuccessReportingServlet extends HttpServlet { * is being built
* MERGE_HEAD/PULLREQUEST_ID is the (optional) sha1 hash that was merged * into, along with the pull request ID
- * + * */ private static final long serialVersionUID = 1L; private final Logger log; @@ -356,7 +356,7 @@ private String getJenkinsUrl(Repository repo, JobTemplate jt, JenkinsServerConfiguration jsc = configurationPersistanceManager .getJenkinsServerConfiguration(rc.getJenkinsServerName()); String key = jt.getBuildNameFor(repo, jsc); - String url = jsc.getUrlForRepo(repo) + "/job/" + key + "/" + String url = jsc.getUrl() + jsc.getUrlForRepo(repo) + "/job/" + key + "/" + Long.toString(buildNumber); return url; } From 1fa3737862926e0bd70286abe4f5f20f33b43c12 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Fri, 16 Oct 2015 15:34:23 -0700 Subject: [PATCH 07/54] Add SSH Creds. Uses Jenkins Creds plugin. -Thank you carl for thinking ahead!!!` --- .../jobtemplate/JenkinsJobXmlFormatter.java | 23 ++++++++++++------- .../JenkinsServerConfigurationImpl.java | 6 +++++ .../stashbot/persistence/JobTemplate.java | 2 -- .../urlbuilder/StashbotUrlBuilder.java | 12 ++++++---- .../static/jenkins-configuration-panel.soy | 16 ++++++------- 5 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java b/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java index 48d9233..70a17e7 100644 --- a/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java +++ b/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java @@ -31,6 +31,7 @@ import com.palantir.stash.stashbot.config.ConfigurationPersistenceService; import com.palantir.stash.stashbot.managers.VelocityManager; import com.palantir.stash.stashbot.persistence.JenkinsServerConfiguration; +import com.palantir.stash.stashbot.persistence.JenkinsServerConfigurationImpl; import com.palantir.stash.stashbot.persistence.JobTemplate; import com.palantir.stash.stashbot.persistence.RepositoryConfiguration; import com.palantir.stash.stashbot.urlbuilder.StashbotUrlBuilder; @@ -82,21 +83,27 @@ public String generateJobXml(JobTemplate jobTemplate, Repository repo) final JenkinsServerConfiguration jsc = cpm .getJenkinsServerConfiguration(rc.getJenkinsServerName()); - RepositoryCloneLinksRequest rclr = - new RepositoryCloneLinksRequest.Builder().repository(repo).protocol("http").user(null).build(); - String repositoryUrl = rs.getCloneLinks(rclr).iterator().next().getHref(); - String cleanRepositoryUrl = repositoryUrl; + + RepositoryCloneLinksRequest rclr = null; + String repositoryUrl = null; + String cleanRepositoryUrl = null; // Handle the various Authentication modes switch (jsc.getAuthenticationMode()) { case USERNAME_AND_PASSWORD: // manually insert the username and pw we are configured to use + rclr = new RepositoryCloneLinksRequest.Builder().repository(repo).protocol("http").user(null).build(); + repositoryUrl = rs.getCloneLinks(rclr).iterator().next().getHref(); + cleanRepositoryUrl = repositoryUrl; repositoryUrl = repositoryUrl.replace("://", "://" + jsc.getStashUsername() + ":" + jsc.getStashPassword() + "@"); break; case CREDENTIAL_MANUALLY_CONFIGURED: - vc.put("credentialUUID", jsc.getStashPassword()); + rclr = new RepositoryCloneLinksRequest.Builder().repository(repo).protocol("ssh").user(null).build(); + repositoryUrl = rs.getCloneLinks(rclr).iterator().next().getHref(); + cleanRepositoryUrl = repositoryUrl; + vc.put("credentialUUID", JenkinsServerConfigurationImpl.convertCredUUID(jsc.getStashPassword(), repo)); break; } vc.put("repositoryUrl", repositoryUrl); @@ -225,7 +232,7 @@ public String generateJobXml(JobTemplate jobTemplate, Repository repo) /** * XML specific parameter types - * + * * @author cmyers */ public static enum JenkinsBuildParamType { @@ -236,9 +243,9 @@ public static enum JenkinsBuildParamType { /** * Appends the shell magics to the build command to make it succeed/fail * properly. - * + * * TODO: move this into the template? - * + * * @param command * @return */ diff --git a/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfigurationImpl.java b/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfigurationImpl.java index f78373f..c449abd 100644 --- a/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfigurationImpl.java +++ b/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfigurationImpl.java @@ -44,4 +44,10 @@ public String getUrlForRepo(Repository r) { return ""; } } + + public static String convertCredUUID (String password, Repository r) { + password = password.replaceAll("\\$project", r.getProject().getKey()); + password = password.replaceAll("\\$repo", r.getSlug()); + return password; + } } diff --git a/src/main/java/com/palantir/stash/stashbot/persistence/JobTemplate.java b/src/main/java/com/palantir/stash/stashbot/persistence/JobTemplate.java index 638218f..d4e6554 100644 --- a/src/main/java/com/palantir/stash/stashbot/persistence/JobTemplate.java +++ b/src/main/java/com/palantir/stash/stashbot/persistence/JobTemplate.java @@ -30,8 +30,6 @@ @Implementation(JobTemplateImpl.class) public interface JobTemplate extends Entity { - - @NotNull @Unique public String getName(); diff --git a/src/main/java/com/palantir/stash/stashbot/urlbuilder/StashbotUrlBuilder.java b/src/main/java/com/palantir/stash/stashbot/urlbuilder/StashbotUrlBuilder.java index 5677c3c..e0af560 100644 --- a/src/main/java/com/palantir/stash/stashbot/urlbuilder/StashbotUrlBuilder.java +++ b/src/main/java/com/palantir/stash/stashbot/urlbuilder/StashbotUrlBuilder.java @@ -67,19 +67,21 @@ public String buildReportingUrl(Repository repo, JobType jobType, } public String buildCloneUrl(Repository repo, JenkinsServerConfiguration jsc) { - RepositoryCloneLinksRequest rclr = - new RepositoryCloneLinksRequest.Builder().repository(repo).protocol("http").user(null).build(); - String url = rs.getCloneLinks(rclr).iterator().next().getHref(); + RepositoryCloneLinksRequest rclr = null; + String url = null; + // we build without username because we insert username AND password, and need both, in the case where we are using USERNAME_AND_PASSWORD. switch (jsc.getAuthenticationMode()) { case USERNAME_AND_PASSWORD: + rclr = new RepositoryCloneLinksRequest.Builder().repository(repo).protocol("http").user(null).build(); + url = rs.getCloneLinks(rclr).iterator().next().getHref(); url = url.replace("://", "://" + mask(jsc.getStashUsername()) + ":" + mask(jsc.getStashPassword()) + "@"); break; case CREDENTIAL_MANUALLY_CONFIGURED: - // do nothing - // XXX: do we need to get the git/ssh link instead of the http link here? maybe that's a new mode? + rclr = new RepositoryCloneLinksRequest.Builder().repository(repo).protocol("ssh").user(null).build(); + url = rs.getCloneLinks(rclr).iterator().next().getHref(); break; default: throw new IllegalStateException("Invalid value - update this code after adding an authentication mode"); diff --git a/src/main/resources/static/jenkins-configuration-panel.soy b/src/main/resources/static/jenkins-configuration-panel.soy index 3c29c67..d763f41 100644 --- a/src/main/resources/static/jenkins-configuration-panel.soy +++ b/src/main/resources/static/jenkins-configuration-panel.soy @@ -63,13 +63,6 @@ {param value: $jenkinsConfig.url /} {param descriptionText: stash_i18n('stash.web.stash.jenkins.url.description', 'Jenkins URL (e.g. http://jenkins.example.com:1234/)') /} {/call} - {call aui.form.selectField} - {param id: 'authenticationMode' /} - {param labelContent: stash_i18n('stash.web.stash.authenticationMode.label', 'Stash Authentication Mode') /} - {param options: $authenticationModeData[$jenkinsConfig.name] /} - {param value: $jenkinsConfig.authenticationModeStr /} - {param descriptionText: stash_i18n('stash.web.stash.authenticationMode.description', 'Stash Authentication Mode') /} - {/call} {call aui.form.textField} {param id: 'username' /} {param labelContent: stash_i18n('stash.web.stash.jenkins.username.label', 'Jenkins Username') /} @@ -82,6 +75,13 @@ {param value: $jenkinsConfig.password /} {param descriptionText: stash_i18n('stash.web.stash.jenkins.description', 'Jenkins Password') /} {/call} + {call aui.form.selectField} + {param id: 'authenticationMode' /} + {param labelContent: stash_i18n('stash.web.stash.authenticationMode.label', 'Stash Authentication Mode') /} + {param options: $authenticationModeData[$jenkinsConfig.name] /} + {param value: $jenkinsConfig.authenticationModeStr /} + {param descriptionText: stash_i18n('stash.web.stash.authenticationMode.description', 'Stash Authentication Mode') /} + {/call} {call aui.form.textField} {param id: 'stashUsername' /} {param labelContent: stash_i18n('stash.web.stash.jenkins.stashusername.label', 'Stash Username') /} @@ -92,7 +92,7 @@ {param id: 'stashPassword' /} {param labelContent: stash_i18n('stash.web.stash.jenkins.stashpassword.label', 'Stash Password') /} {param value: $jenkinsConfig.stashPassword /} - {param descriptionText: stash_i18n('stash.web.stash.jenkins.stashpassword.description', 'Stash Password') /} + {param descriptionText: stash_i18n('stash.web.stash.jenkins.stashpassword.description', 'Stash Password. Supports $password and $repo replacement in Credential UUID mode.') /} {/call} {call aui.form.textField} {param id: 'maxVerifyChain' /} From 23341c752861380835cbe5a0dfb1192391645508 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Wed, 21 Oct 2015 11:40:50 -0700 Subject: [PATCH 08/54] Remove the fetch. Jenkins GIT seems to grab all the commits anyways -Jenkins credentials don't seem to transfer to env which is good --- src/main/resources/jenkins-verify-pull-request-job.vm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/jenkins-verify-pull-request-job.vm b/src/main/resources/jenkins-verify-pull-request-job.vm index da1ac8e..cfd8e60 100644 --- a/src/main/resources/jenkins-verify-pull-request-job.vm +++ b/src/main/resources/jenkins-verify-pull-request-job.vm @@ -132,7 +132,7 @@ set +x -git fetch $mergeRefUrl $mergeRef && git merge --no-ff $mergeHead || /bin/false || (echo "BUILD FAILURE$?" && /bin/false) +git merge --no-ff $mergeHead || /bin/false || (echo "BUILD FAILURE$?" && /bin/false) set -x From ae7198123739c529d47d5d08608144a94c33f025 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Fri, 30 Oct 2015 15:42:26 -0700 Subject: [PATCH 09/54] Might need this --- .../stash/stashbot/config/ConfigurationPersistenceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java index 9a7df7c..6930382 100644 --- a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java +++ b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java @@ -207,6 +207,7 @@ public void setJenkinsServerConfiguration(String name, String url, configs[0].setUsername(username); configs[0].setPassword(password); configs[0].setAuthenticationMode(authenticationMode); + configs[0].setAuthenticationModeStr(authenticationMode.toString()); configs[0].setStashUsername(stashUsername); configs[0].setStashPassword(stashPassword); configs[0].setMaxVerifyChain(maxVerifyChain); From 8e3e3ae14a5a3bec3ae21d531ec26cf5e5ce80d9 Mon Sep 17 00:00:00 2001 From: Carl Myers Date: Tue, 16 Jun 2015 15:35:48 -0700 Subject: [PATCH 10/54] Updating for compatibility with stash 3.10.0+ I have no idea why this test passed with stash 3.6 and broke with 3.10.0. It looks like it should have never worked. I am mortified. *shrug* --- pom.xml | 4 ++-- .../palantir/stash/stashbot/mocks/MockJobTemplateFactory.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8d490b0..b8dc300 100644 --- a/pom.xml +++ b/pom.xml @@ -212,8 +212,8 @@ - 3.6.0 - 3.6.0 + 3.10.0 + 3.10.0 tomcat7x (git reset --hard HEAD && git clean -fdx) || (echo "SETUP exited with status $? ; FAILURE1" ; /bin/false) diff --git a/src/main/resources/jenkins-verify-job.vm b/src/main/resources/jenkins-verify-job.vm index ba83e8d..afe5c45 100644 --- a/src/main/resources/jenkins-verify-job.vm +++ b/src/main/resources/jenkins-verify-job.vm @@ -96,6 +96,9 @@ true + + $esc.xml($globalPrebuildCommand) + (git reset --hard HEAD && git clean -fdx) || (echo "SETUP exited with status $? ; FAILURE1" ; /bin/false) diff --git a/src/main/resources/jenkins-verify-pull-request-job.vm b/src/main/resources/jenkins-verify-pull-request-job.vm index 40e9751..76363d3 100644 --- a/src/main/resources/jenkins-verify-pull-request-job.vm +++ b/src/main/resources/jenkins-verify-pull-request-job.vm @@ -119,6 +119,9 @@ true + + $esc.xml($globalPrebuildCommand) + (git reset --hard HEAD && git clean -fdx) || (echo "SETUP exited with status $? ; FAILURE1" ; /bin/false) From 4f7c3506c49f295454df98705a8bfbf9f13733b8 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Wed, 25 Nov 2015 14:55:13 -0800 Subject: [PATCH 32/54] Revert "Another stash 3.11 fix for RepositoryService.getCloneLinks()" This reverts commit b495a02897564871b44de7ca656632c77cee6f40, which apparently causes broken clone URLs like https://jenkins-stash-ro:REAL_PASSWORD@USER_WHO_PRESSED_SAVE@stash.yojoe.local/scm/PROJECT/REPO.git and we don't want the whole '@USER_WHO_PRESSED_SAVE' to be in there. --Elijah --- .../stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java b/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java index 408d563..137879d 100644 --- a/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java +++ b/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java @@ -97,7 +97,7 @@ public String generateJobXml(JobTemplate jobTemplate, Repository repo) switch (jsc.getAuthenticationMode()) { case USERNAME_AND_PASSWORD: // manually insert the username and pw we are configured to use - rclr = new RepositoryCloneLinksRequest.Builder().repository(repo).protocol("http").build(); + rclr = new RepositoryCloneLinksRequest.Builder().repository(repo).protocol("http").user(null).build(); repositoryUrl = rs.getCloneLinks(rclr).iterator().next().getHref(); cleanRepositoryUrl = repositoryUrl; repositoryUrl = repositoryUrl.replace("://", From bc1b9665841cc26d66823f0df0c9a8b2e191bcc7 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Tue, 17 Nov 2015 17:36:51 -0800 Subject: [PATCH 33/54] Do some shifting around and grouping of repo-level options -Tried doing it in two columns while building this commit but settled on single column for verify/publish since we tend to have long commands. It's nice to see more of the command -Tweak a bit of the text for clarification --- src/main/resources/atlassian-plugin.xml | 4 + .../static/repository-configuration-panel.soy | 386 +++++++++++------- 2 files changed, 249 insertions(+), 141 deletions(-) diff --git a/src/main/resources/atlassian-plugin.xml b/src/main/resources/atlassian-plugin.xml index 47fe512..1d57946 100644 --- a/src/main/resources/atlassian-plugin.xml +++ b/src/main/resources/atlassian-plugin.xml @@ -13,6 +13,10 @@ com.atlassian.auiplugin:ajs + com.atlassian.auiplugin:aui-experimental-expander + com.atlassian.auiplugin:aui-experimental-iconfont + com.atlassian.auiplugin:aui-experimental-tooltips + com.atlassian.auiplugin:aui-form-notification diff --git a/src/main/resources/static/repository-configuration-panel.soy b/src/main/resources/static/repository-configuration-panel.soy index 110b778..a221404 100644 --- a/src/main/resources/static/repository-configuration-panel.soy +++ b/src/main/resources/static/repository-configuration-panel.soy @@ -64,15 +64,12 @@ {/param} {/call} {/if} -{call aui.group.group} - {param content} - {call aui.form.form} - {param action: '' /} - {param content} -

Local Settings

-

These are settings modify if the repo builds, where it builds, and when it builds.

+{call aui.form.form} + {param action: '' /} + {param content} + {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.enable-ci-radio.button.description', 'Stashbot Repository Enable
(to enable stashbot for this repository, check this and at least one build type below)') /} + {param legendContent: stash_i18n('stash.web.stash.enable-ci-radio.button.description', 'Stashbot') /} {param fields: [[ 'id': 'ciEnabled', 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), @@ -80,22 +77,16 @@ ]] /} {/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.enable-verification.button.description', 'Commit Verifies') /} - {param fields: [[ - 'id': 'verificationEnabled', - 'labelText': stash_i18n('stash.web.stash.enable-verification.button.label', 'Enabled'), - 'isChecked': $verificationEnabled - ]] /} - {/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.enable-verify-pr.button.description', 'Pull Request Verifies') /} - {param fields: [[ - 'id': 'verifyPREnabled', - 'labelText': stash_i18n('stash.web.stash.enable-verify-pr.button.label', 'Enabled'), - 'isChecked': $verifyPREnabled - ]] /} - {/call} + + + + + +{call aui.group.group} + {param content} + {call aui.group.item} + {param content} +

Publishing

{call aui.form.checkboxField} {param legendContent: stash_i18n('stash.web.stash.enable-publish.button.description', 'Publishes') /} {param fields: [[ @@ -104,112 +95,172 @@ 'isChecked': $publishEnabled ]] /} {/call} - - {call aui.form.selectField} - {param id: 'jenkinsServerName' /} - {param labelContent: stash_i18n('stash.web.stash.jenkinsServerName.label', 'Jenkins Server') /} - {param options: $jenkinsServersData /} - {param value: $jenkinsServerName /} - {/call} {call aui.form.textField} {param id: 'publishBranchRegex' /} - {param labelContent: stash_i18n('stash.web.stash.publishBranchRegex.label', 'Regex for branches to publish') /} + {param labelContent: stash_i18n('stash.web.stash.publishBranchRegex.label', 'Regex') /} {param value: $publishBranchRegex /} - {param descriptionText: stash_i18n('stash.web.stash.publishBranchRegex.description', '(anchored regular expression) Branches matching this regular expression will have the publish build command run on them by jenkins (e.g. "refs/heads/master" or "refs/heads/.*")') /} + {param fieldWidth: 'long' /} + {param descriptionText: stash_i18n('stash.web.stash.publishBranchRegex.description', 'Branches that match this regex will have a publish build run on Jenkins. Be aware that this is the full git ref - e.g. "refs/heads/master" or "refs/heads/.*", not just "master".') /} + {param extraAttributes: 'data-aui-notification-field data-aui-notification-info="Branches that match this regex will have a publish build run on Jenkins. Be aware that this is the full git ref - e.g. "refs/heads/master" or "refs/heads/.*", not just "master"."' /} {/call} {call aui.form.textField} - {param id: 'verifyBranchRegex' /} - {param labelContent: stash_i18n('stash.web.stash.verifyBranchRegex.label', 'Regex for branches to verify') /} - {param value: $verifyBranchRegex /} - {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', '(anchored regular expression) Branches matching this regular expression will have the verify build command run on them by jenkins (e.g. "refs/heads/develop" or "refs/heads/.*")') /} + {param id: 'publishBuildCommand' /} + {param labelContent: stash_i18n('stash.web.stash.publishBuildCommand.label', 'Command to run') /} + {param value: $publishBuildCommand /} + {param fieldWidth: 'long' /} {/call} - {call aui.form.textField} - {param id: 'maxVerifyChain' /} - {param labelContent: stash_i18n('stash.web.stash.maxVerifyChain.label', 'Maximum number of commits to verify on a single push') /} - {param value: $maxVerifyChain /} + {call aui.expander.trigger} + {param id: 'advanced-expand-publish'/} + {param contentId: 'advanced-expand-publish-content'/} + {param tag: 'a'/} + {param content: 'Advanced Publishing'/} + {param replaceText: 'Hide Advanced'/} {/call} + {call aui.expander.content} + {param id: 'advanced-expand-publish-content'/} + {param content} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.publish-pinned-checkbox.button.description', 'Restrict Jenkins Nodes') /} + {param fields: [[ + 'id': 'isPublishPinned', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $isPublishPinned + ]] /} + {/call} + {call aui.form.textField} + {param id: 'publishLabel' /} + {param labelContent: stash_i18n('stash.web.stash.verifyLabel.label', 'Jenkins Node Label') /} + {param value: $publishLabel /} + {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Jenkins node label to restrict publish builds to') /} + {/call} + {/param} + {/call} + + + {/param} + {/call} + {/param} +{/call} + + +{call aui.group.group} + {param content} + +

Verifications

{call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.rebuild-on-update-radio.button.description', 'Rebuild merge verifies when target branch updates') /} + {param legendContent: stash_i18n('stash.web.stash.enable-verification.button.description', 'Commit Verifies') /} {param fields: [[ - 'id': 'rebuildOnUpdate', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), - 'isChecked': $rebuildOnUpdate + 'id': 'verificationEnabled', + 'labelText': stash_i18n('stash.web.stash.enable-verification.button.label', 'Enabled'), + 'isChecked': $verificationEnabled ]] /} {/call} {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.strict-verify-mode.button.description', 'Enable Strict Verify Mode') /} + {param legendContent: stash_i18n('stash.web.stash.enable-verify-pr.button.description', 'PR Verifies') /} {param fields: [[ - 'id': 'isStrictVerifyMode', - 'labelText': stash_i18n('stash.web.stash.strict-verify-mode.button.label', 'Enabled'), - 'isChecked': $isStrictVerifyMode + 'id': 'verifyPREnabled', + 'labelText': stash_i18n('stash.web.stash.enable-verify-pr.button.label', 'Enabled'), + 'isChecked': $verifyPREnabled ]] /} {/call} - - -

Jenkins Settings

-

Modify what commands get run for builds, if there are tests, who gets notified, etc.

- {call aui.form.textField} - {param id: 'buildTimeout' /} - {param labelContent: stash_i18n('stash.web.stash.buildTimeout.label', 'Build Timeout (minutes)') /} - {param descriptionText: stash_i18n('stash.web.stash.buildTimeout.description', 'Timeout (in minutes) for all builds. A value of -1 uses Jenkins-wide default. Minimum value of ' + $buildTimeoutMin + ' and maximum value of ' + $buildTimeoutMax + '.') /} - {param value: $buildTimeout /} - {/call} - -

 

- - {call aui.form.textField} - {param id: 'prebuildCommand' /} - {param labelContent: stash_i18n('stash.web.stash.prebuildCommand.label', 'Command to run before all builds (and for PR verifies, before the merge)') /} - {param value: $prebuildCommand /} + {param id: 'verifyBranchRegex' /} + {param labelContent: stash_i18n('stash.web.stash.verifyBranchRegex.label', 'Regex') /} + {param value: $verifyBranchRegex /} + {param fieldWidth: 'long' /} + {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Branches that match this regex will have a verify build run on Jenkins. Be aware that this is the full git ref - e.g. "refs/heads/master" or "refs/heads/.*", not just "master".') /} {/call} - -

 

- {call aui.form.textField} - {param id: 'publishBuildCommand' /} - {param labelContent: stash_i18n('stash.web.stash.publishBuildCommand.label', 'Command to run for a publish build') /} - {param value: $publishBuildCommand /} + {param id: 'verifyBuildCommand' /} + {param labelContent: stash_i18n('stash.web.stash.verifyBuildCommand.label', 'Command to run') /} + {param value: $verifyBuildCommand /} + {param fieldWidth: 'long' /} {/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.publish-pinned-checkbox.button.description', 'Pin publish builds to a label') /} - {param fields: [[ - 'id': 'isPublishPinned', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Pinned'), - 'isChecked': $isPublishPinned - ]] /} + {call aui.expander.trigger} + {param id: 'advanced-expand-verify'/} + {param contentId: 'advanced-expand-verify-content'/} + {param tag: 'a'/} + {param content: 'Advanced Verification'/} + {param replaceText: 'Hide Advanced'/} {/call} - {call aui.form.textField} - {param id: 'publishLabel' /} - {param labelContent: stash_i18n('stash.web.stash.verifyLabel.label', 'Label to pin publish builds to') /} - {param value: $publishLabel /} - {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Build node label to pin publish builds to') /} + {call aui.expander.content} + {param id: 'advanced-expand-verify-content'/} + {param content} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.verify-pinned-checkbox.button.description', 'Restrict Jenkins Nodes') /} + {param fields: [[ + 'id': 'isVerifyPinned', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $isVerifyPinned + ]] /} + {/call} + {call aui.form.textField} + {param id: 'verifyLabel' /} + {param labelContent: stash_i18n('stash.web.stash.verifyLabel.label', 'Jenkins Node Label') /} + {param value: $verifyLabel /} + {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Jenkins node label to restrict publish builds to') /} + {/call} + {call aui.form.textField} + {param id: 'maxVerifyChain' /} + {param labelContent: stash_i18n('stash.web.stash.maxVerifyChain.label', 'Maximum number of commits to verify on a single push') /} + {param value: $maxVerifyChain /} + {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.rebuild-on-update-radio.button.description', 'Rebuild PR on target change') /} + {param fields: [[ + 'id': 'rebuildOnUpdate', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $rebuildOnUpdate + ]] /} + {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.strict-verify-mode.button.description', 'Strict Verify Mode') /} + {param fields: [[ + 'id': 'isStrictVerifyMode', + 'labelText': stash_i18n('stash.web.stash.strict-verify-mode.button.label', 'Enabled'), + 'isChecked': $isStrictVerifyMode + ]] /} + {/call} + {/param} {/call} -

 

+ {/param} +{/call} - {call aui.form.textField} - {param id: 'verifyBuildCommand' /} - {param labelContent: stash_i18n('stash.web.stash.verifyBuildCommand.label', 'Command to run for a verify build') /} - {param value: $verifyBuildCommand /} - {/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.verify-pinned-checkbox.button.description', 'Pin verify builds to a label') /} - {param fields: [[ - 'id': 'isVerifyPinned', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Pinned'), - 'isChecked': $isVerifyPinned - ]] /} +

Jenkins

+{call aui.group.group} + {param content} + {/param} +{/call} + + {call aui.group.group} + {param content} + {call aui.form.selectField} + {param id: 'jenkinsServerName' /} + {param labelContent: stash_i18n('stash.web.stash.jenkinsServerName.label', 'Jenkins Server') /} + {param options: $jenkinsServersData /} + {param value: $jenkinsServerName /} {/call} + {/param} + {/call} + +

 

+ + {call aui.group.group} + {param content} {call aui.form.textField} - {param id: 'verifyLabel' /} - {param labelContent: stash_i18n('stash.web.stash.verifyLabel.label', 'Label to pin verify builds to') /} - {param value: $verifyLabel /} - {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Build node label to pin verify builds to') /} + {param id: 'buildTimeout' /} + {param labelContent: stash_i18n('stash.web.stash.buildTimeout.label', 'Build Timeout (minutes)') /} + {param descriptionText: stash_i18n('stash.web.stash.buildTimeout.description', 'Timeout (in minutes) for all builds. A value of -1 uses Jenkins-wide default. Minimum value of ' + $buildTimeoutMin + ' and maximum value of ' + $buildTimeoutMax + '.') /} + {param value: $buildTimeout /} {/call} + {/param} + {/call} -

 

+

 

+{call aui.group.group} + {param content} {call aui.form.checkboxField} {param legendContent: stash_i18n('stash.web.stash.junit-enabled.button.description', 'Junit Test Results') /} {param fields: [[ @@ -224,25 +275,71 @@ {param value: $junitPath /} {param descriptionText: stash_i18n('stash.web.stash.junitPath.description', 'Path to use for junit results (e.g. build/test-results/*.xml)') /} {/call} + {/param} +{/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.artifacts-enabled.button.description', 'Artifact Archival') /} - {param fields: [[ - 'id': 'artifactsEnabled', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), - 'isChecked': $artifactsEnabled +{call aui.expander.trigger} + {param id: 'advanced-expand-jenkins'/} + {param contentId: 'advanced-expand-jenkins-content'/} + {param tag: 'a'/} + {param content: 'Advanced Jenkins'/} + {param replaceText: 'Hide Advanced'/} +{/call} +{call aui.expander.content} + {param id: 'advanced-expand-jenkins-content'/} + {param content} + + {call aui.group.group} + {param content} + {call aui.form.textField} + {param id: 'prebuildCommand' /} + {param labelContent: stash_i18n('stash.web.stash.prebuildCommand.label', 'Command to run before all builds (and for PR verifies, before the merge)') /} + {param value: $prebuildCommand /} + {/call} + {/param} + {/call} + +

 

+ + {call aui.group.group} + {param content} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.preserveJenkinsJobConfig.label', 'Preserve Jenkins Job Config') /} + {param descriptionText: stash_i18n('stash.web.stash.preserveJenkinsJobConfig.description', 'See below for details.') /} + {param fields: [[ + 'id': 'isPreserveJenkinsJobConfig', + 'labelText': stash_i18n('stash.web.stash.preserveJenkinsJobConfig.button.label', 'Enabled'), + 'isChecked': $isPreserveJenkinsJobConfig ]] /} - {/call} - {call aui.form.textField} - {param id: 'artifactsPath' /} - {param labelContent: stash_i18n('stash.web.stash.artifactsPath.label', 'Artifact Location') /} - {param value: $artifactsPath /} - {param descriptionText: stash_i18n('stash.web.stash.artifactsPath.description', 'Path to use for artifacts (e.g. **/*.log)') /} - {/call} + {/call} + {/param} + {/call} +

 

+ + {call aui.group.group} + {param content} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.artifacts-enabled.button.description', 'Artifact Archival') /} + {param fields: [[ + 'id': 'artifactsEnabled', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $artifactsEnabled + ]] /} + {/call} + {call aui.form.textField} + {param id: 'artifactsPath' /} + {param labelContent: stash_i18n('stash.web.stash.artifactsPath.label', 'Artifact Location') /} + {param value: $artifactsPath /} + {param descriptionText: stash_i18n('stash.web.stash.artifactsPath.description', 'Path to use for artifacts (e.g. **/*.log)') /} + {/call} + {/param} + {/call} -

 

+

 

+ {call aui.group.group} + {param content} {call aui.form.checkboxField} {param legendContent: stash_i18n('stash.web.stash.email-enabled.button.description', 'E-mail notifications') /} {param fields: [[ @@ -280,38 +377,42 @@ 'isChecked': $isEmailPerModuleEmail ]] /} {/call} + {/param} + {/call} -

 

+ {/param} +{/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.preserveJenkinsJobConfig.label', 'Preserve Jenkins Job Config') /} - {param descriptionText: stash_i18n('stash.web.stash.preserveJenkinsJobConfig.description', 'See below for details.') /} - {param fields: [[ - 'id': 'isPreserveJenkinsJobConfig', - 'labelText': stash_i18n('stash.web.stash.preserveJenkinsJobConfig.button.label', 'Enabled'), - 'isChecked': $isPreserveJenkinsJobConfig - ]] /} - {/call} -

 

+

 

- {call aui.form.buttons} - {param content} - {call aui.form.submit} - {param id: 'submit' /} - {param text: stash_i18n('stash.web.stash.ci-prefs.submit', 'Save') /} - {param type: 'submit' /} - {/call} - {/param} - {/call} +{call aui.form.buttons} + {param content} + {call aui.form.submit} + {param id: 'submit' /} + {param text: stash_i18n('stash.web.stash.ci-prefs.submit', 'Save') /} + {param type: 'submit' /} + {/call} + {/param} +{/call} -

-
- {/param} - {/call} - {/param} + + {/param} +{/call} + +

+{call aui.expander.trigger} + {param id: 'advanced-expand-documentation'/} + {param contentId: 'advanced-expand-documentation-content'/} + {param tag: 'a'/} + {param content: 'Show Documentation'/} + {param replaceText: 'Hide Documentation'/} {/call} +

+{call aui.expander.content} + {param id: 'advanced-expand-documentation-content'/} + {param content}

Regular Expressions:

@@ -354,6 +455,9 @@ Note also that the configuration should take into account capacity concerns. If When you click save, normally, the job is destroyed and recreated. This option disables that, so editing this page still changes internal state but no longer effects a job (that already exists) at all. Some featuers will work, some will not, but changes made directly in jenkins will no longer be destroyed.

+{/param} +{/call} + From 769ce8a33c0cf13890024b7a281ff226aff78714 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Wed, 18 Nov 2015 12:30:24 -0800 Subject: [PATCH 34/54] Expose the tests + artifacts all the time and hide everything else --- .../static/repository-configuration-panel.soy | 133 ++++++++++-------- 1 file changed, 78 insertions(+), 55 deletions(-) diff --git a/src/main/resources/static/repository-configuration-panel.soy b/src/main/resources/static/repository-configuration-panel.soy index a221404..45310cf 100644 --- a/src/main/resources/static/repository-configuration-panel.soy +++ b/src/main/resources/static/repository-configuration-panel.soy @@ -227,13 +227,71 @@ {/param} {/call} -

Jenkins

+

Artifacts

{call aui.group.group} {param content} + + {call aui.group.item} + {param content} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.junit-enabled.button.description', 'Junit Test Results') /} + {param fields: [[ + 'id': 'isJunit', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $isJunit + ]] /} + {/call} + {call aui.form.textField} + {param id: 'junitPath' /} + {param labelContent: stash_i18n('stash.web.stash.junitPath.label', 'Result Location') /} + {param value: $junitPath /} + {param descriptionText: stash_i18n('stash.web.stash.junitPath.description', 'Path to use for junit results (e.g. build/test-results/*.xml)') /} + {/call} + {/param} + {/call} + + + {call aui.group.item} + {param content} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.artifacts-enabled.button.description', 'Artifact Archival') /} + {param fields: [[ + 'id': 'artifactsEnabled', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $artifactsEnabled + ]] /} + {/call} + {call aui.form.textField} + {param id: 'artifactsPath' /} + {param labelContent: stash_i18n('stash.web.stash.artifactsPath.label', 'Artifact Location') /} + {param value: $artifactsPath /} + {param descriptionText: stash_i18n('stash.web.stash.artifactsPath.description', 'Path to use for artifacts (e.g. **/*.log)') /} + {/call} + {/param} + {/call} + {/param} {/call} +

Advanced

+ + + +{call aui.expander.trigger} + {param id: 'advanced-expand-jenkins'/} + {param contentId: 'advanced-expand-jenkins-content'/} + {param tag: 'a'/} + {param content: 'Show Advanced'/} + {param replaceText: 'Hide Advanced'/} +{/call} +{call aui.expander.content} + {param id: 'advanced-expand-jenkins-content'/} + {param content} + {call aui.group.group} + {param content} + + {call aui.group.item} {param content} {call aui.form.selectField} {param id: 'jenkinsServerName' /} @@ -244,64 +302,43 @@ {/param} {/call} -

 

- - {call aui.group.group} + {call aui.group.item} {param content} {call aui.form.textField} {param id: 'buildTimeout' /} {param labelContent: stash_i18n('stash.web.stash.buildTimeout.label', 'Build Timeout (minutes)') /} {param descriptionText: stash_i18n('stash.web.stash.buildTimeout.description', 'Timeout (in minutes) for all builds. A value of -1 uses Jenkins-wide default. Minimum value of ' + $buildTimeoutMin + ' and maximum value of ' + $buildTimeoutMax + '.') /} {param value: $buildTimeout /} + {param fieldWidth: 'short' /} {/call} {/param} {/call} - -

 

+ + {/param} + {/call} {call aui.group.group} {param content} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.junit-enabled.button.description', 'Junit Test Results') /} - {param fields: [[ - 'id': 'isJunit', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), - 'isChecked': $isJunit - ]] /} - {/call} - {call aui.form.textField} - {param id: 'junitPath' /} - {param labelContent: stash_i18n('stash.web.stash.junitPath.label', 'Result Location') /} - {param value: $junitPath /} - {param descriptionText: stash_i18n('stash.web.stash.junitPath.description', 'Path to use for junit results (e.g. build/test-results/*.xml)') /} - {/call} +

 

{/param} {/call} -{call aui.expander.trigger} - {param id: 'advanced-expand-jenkins'/} - {param contentId: 'advanced-expand-jenkins-content'/} - {param tag: 'a'/} - {param content: 'Advanced Jenkins'/} - {param replaceText: 'Hide Advanced'/} -{/call} -{call aui.expander.content} - {param id: 'advanced-expand-jenkins-content'/} - {param content} - {call aui.group.group} {param content} + + {call aui.group.item} + {param content} {call aui.form.textField} {param id: 'prebuildCommand' /} - {param labelContent: stash_i18n('stash.web.stash.prebuildCommand.label', 'Command to run before all builds (and for PR verifies, before the merge)') /} + {param descriptionText: stash_i18n('stash.web.stash.prebuildCommand.description', 'Command to run before all builds (and for PR verifies, before the merge)') /} + {param labelContent: stash_i18n('stash.web.stash.prebuildCommand.label', 'Pre Build Command') /} {param value: $prebuildCommand /} {/call} {/param} {/call} -

 

- {call aui.group.group} + {call aui.group.item} {param content} {call aui.form.checkboxField} {param legendContent: stash_i18n('stash.web.stash.preserveJenkinsJobConfig.label', 'Preserve Jenkins Job Config') /} @@ -314,29 +351,15 @@ {/call} {/param} {/call} - -

 

- - {call aui.group.group} - {param content} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.artifacts-enabled.button.description', 'Artifact Archival') /} - {param fields: [[ - 'id': 'artifactsEnabled', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), - 'isChecked': $artifactsEnabled - ]] /} - {/call} - {call aui.form.textField} - {param id: 'artifactsPath' /} - {param labelContent: stash_i18n('stash.web.stash.artifactsPath.label', 'Artifact Location') /} - {param value: $artifactsPath /} - {param descriptionText: stash_i18n('stash.web.stash.artifactsPath.description', 'Path to use for artifacts (e.g. **/*.log)') /} - {/call} - {/param} + + {/param} {/call} -

 

+{call aui.group.group} + {param content} +

 

+ {/param} +{/call} {call aui.group.group} {param content} From 8b9e7df8df16d56852d6e6a9f418e143550bbae5 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Wed, 18 Nov 2015 13:08:27 -0800 Subject: [PATCH 35/54] Spacing and a few text fixes --- .../static/repository-configuration-panel.soy | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/resources/static/repository-configuration-panel.soy b/src/main/resources/static/repository-configuration-panel.soy index 45310cf..8ff0781 100644 --- a/src/main/resources/static/repository-configuration-panel.soy +++ b/src/main/resources/static/repository-configuration-panel.soy @@ -81,12 +81,11 @@ - +

Publishing

{call aui.group.group} {param content} {call aui.group.item} {param content} -

Publishing

{call aui.form.checkboxField} {param legendContent: stash_i18n('stash.web.stash.enable-publish.button.description', 'Publishes') /} {param fields: [[ @@ -97,7 +96,7 @@ {/call} {call aui.form.textField} {param id: 'publishBranchRegex' /} - {param labelContent: stash_i18n('stash.web.stash.publishBranchRegex.label', 'Regex') /} + {param labelContent: stash_i18n('stash.web.stash.publishBranchRegex.label', 'Trigger Regex') /} {param value: $publishBranchRegex /} {param fieldWidth: 'long' /} {param descriptionText: stash_i18n('stash.web.stash.publishBranchRegex.description', 'Branches that match this regex will have a publish build run on Jenkins. Be aware that this is the full git ref - e.g. "refs/heads/master" or "refs/heads/.*", not just "master".') /} @@ -105,7 +104,8 @@ {/call} {call aui.form.textField} {param id: 'publishBuildCommand' /} - {param labelContent: stash_i18n('stash.web.stash.publishBuildCommand.label', 'Command to run') /} + {param labelContent: stash_i18n('stash.web.stash.publishBuildCommand.label', 'Command') /} + {param descriptionText: stash_i18n('stash.web.stash.publishBuildCommand.description', 'The command that will be run during a publish build. For example: ./scripts/publish.sh') /} {param value: $publishBuildCommand /} {param fieldWidth: 'long' /} {/call} @@ -143,10 +143,11 @@ {/call} +

Verifications

{call aui.group.group} {param content} -

Verifications

+ {call aui.form.checkboxField} {param legendContent: stash_i18n('stash.web.stash.enable-verification.button.description', 'Commit Verifies') /} {param fields: [[ @@ -165,14 +166,15 @@ {/call} {call aui.form.textField} {param id: 'verifyBranchRegex' /} - {param labelContent: stash_i18n('stash.web.stash.verifyBranchRegex.label', 'Regex') /} + {param labelContent: stash_i18n('stash.web.stash.verifyBranchRegex.label', 'Trigger Regex') /} {param value: $verifyBranchRegex /} {param fieldWidth: 'long' /} {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Branches that match this regex will have a verify build run on Jenkins. Be aware that this is the full git ref - e.g. "refs/heads/master" or "refs/heads/.*", not just "master".') /} {/call} {call aui.form.textField} {param id: 'verifyBuildCommand' /} - {param labelContent: stash_i18n('stash.web.stash.verifyBuildCommand.label', 'Command to run') /} + {param labelContent: stash_i18n('stash.web.stash.verifyBuildCommand.label', 'Command') /} + {param descriptionText: stash_i18n('stash.web.stash.verifyBuildCommand.description', 'The command that will be run during a verification or verify_pr build. For example: ./scripts/verify.sh') /} {param value: $verifyBuildCommand /} {param fieldWidth: 'long' /} {/call} From 6f22a0cfaf3983ed5d44607a407ce4db73c7ac00 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Wed, 18 Nov 2015 17:14:10 -0800 Subject: [PATCH 36/54] Fix up all the whitespace and indenting --- .../static/repository-configuration-panel.soy | 775 +++++++++--------- 1 file changed, 381 insertions(+), 394 deletions(-) diff --git a/src/main/resources/static/repository-configuration-panel.soy b/src/main/resources/static/repository-configuration-panel.soy index 8ff0781..c7f1658 100644 --- a/src/main/resources/static/repository-configuration-panel.soy +++ b/src/main/resources/static/repository-configuration-panel.soy @@ -57,433 +57,420 @@ {/param} {/call} {if $error} - {call widget.aui.message.error} - {param title: 'An error occured in saving config.' /} - {param content} - {$error} - {/param} - {/call} + {call widget.aui.message.error} + {param title: 'An error occured in saving config.' /} + {param content} + {$error} + {/param} + {/call} {/if} {call aui.form.form} - {param action: '' /} - {param content} - - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.enable-ci-radio.button.description', 'Stashbot') /} - {param fields: [[ - 'id': 'ciEnabled', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), - 'isChecked': $ciEnabled - ]] /} - {/call} - - - - + {param action: '' /} + {param content} -

Publishing

-{call aui.group.group} - {param content} - {call aui.group.item} - {param content} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.enable-publish.button.description', 'Publishes') /} - {param fields: [[ - 'id': 'publishEnabled', - 'labelText': stash_i18n('stash.web.stash.enable-verification.button.label', 'Enabled'), - 'isChecked': $publishEnabled - ]] /} - {/call} - {call aui.form.textField} - {param id: 'publishBranchRegex' /} - {param labelContent: stash_i18n('stash.web.stash.publishBranchRegex.label', 'Trigger Regex') /} - {param value: $publishBranchRegex /} - {param fieldWidth: 'long' /} - {param descriptionText: stash_i18n('stash.web.stash.publishBranchRegex.description', 'Branches that match this regex will have a publish build run on Jenkins. Be aware that this is the full git ref - e.g. "refs/heads/master" or "refs/heads/.*", not just "master".') /} - {param extraAttributes: 'data-aui-notification-field data-aui-notification-info="Branches that match this regex will have a publish build run on Jenkins. Be aware that this is the full git ref - e.g. "refs/heads/master" or "refs/heads/.*", not just "master"."' /} - {/call} - {call aui.form.textField} - {param id: 'publishBuildCommand' /} - {param labelContent: stash_i18n('stash.web.stash.publishBuildCommand.label', 'Command') /} - {param descriptionText: stash_i18n('stash.web.stash.publishBuildCommand.description', 'The command that will be run during a publish build. For example: ./scripts/publish.sh') /} - {param value: $publishBuildCommand /} - {param fieldWidth: 'long' /} - {/call} - {call aui.expander.trigger} - {param id: 'advanced-expand-publish'/} - {param contentId: 'advanced-expand-publish-content'/} - {param tag: 'a'/} - {param content: 'Advanced Publishing'/} - {param replaceText: 'Hide Advanced'/} - {/call} - {call aui.expander.content} - {param id: 'advanced-expand-publish-content'/} + {call aui.group.group} {param content} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.publish-pinned-checkbox.button.description', 'Restrict Jenkins Nodes') /} - {param fields: [[ - 'id': 'isPublishPinned', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), - 'isChecked': $isPublishPinned - ]] /} - {/call} - {call aui.form.textField} - {param id: 'publishLabel' /} - {param labelContent: stash_i18n('stash.web.stash.verifyLabel.label', 'Jenkins Node Label') /} - {param value: $publishLabel /} - {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Jenkins node label to restrict publish builds to') /} - {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.enable-ci-radio.button.description', 'Stashbot') /} + {param fields: [[ + 'id': 'ciEnabled', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $ciEnabled + ]] /} + {/call} {/param} - {/call} - - - {/param} - {/call} - {/param} -{/call} - + {/call} /* Top group end */ -

Verifications

-{call aui.group.group} - {param content} - - - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.enable-verification.button.description', 'Commit Verifies') /} - {param fields: [[ - 'id': 'verificationEnabled', - 'labelText': stash_i18n('stash.web.stash.enable-verification.button.label', 'Enabled'), - 'isChecked': $verificationEnabled - ]] /} - {/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.enable-verify-pr.button.description', 'PR Verifies') /} - {param fields: [[ - 'id': 'verifyPREnabled', - 'labelText': stash_i18n('stash.web.stash.enable-verify-pr.button.label', 'Enabled'), - 'isChecked': $verifyPREnabled - ]] /} - {/call} - {call aui.form.textField} - {param id: 'verifyBranchRegex' /} - {param labelContent: stash_i18n('stash.web.stash.verifyBranchRegex.label', 'Trigger Regex') /} - {param value: $verifyBranchRegex /} - {param fieldWidth: 'long' /} - {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Branches that match this regex will have a verify build run on Jenkins. Be aware that this is the full git ref - e.g. "refs/heads/master" or "refs/heads/.*", not just "master".') /} - {/call} - {call aui.form.textField} - {param id: 'verifyBuildCommand' /} - {param labelContent: stash_i18n('stash.web.stash.verifyBuildCommand.label', 'Command') /} - {param descriptionText: stash_i18n('stash.web.stash.verifyBuildCommand.description', 'The command that will be run during a verification or verify_pr build. For example: ./scripts/verify.sh') /} - {param value: $verifyBuildCommand /} - {param fieldWidth: 'long' /} - {/call} - {call aui.expander.trigger} - {param id: 'advanced-expand-verify'/} - {param contentId: 'advanced-expand-verify-content'/} - {param tag: 'a'/} - {param content: 'Advanced Verification'/} - {param replaceText: 'Hide Advanced'/} - {/call} - {call aui.expander.content} - {param id: 'advanced-expand-verify-content'/} +

Publishing

+ {call aui.group.group} {param content} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.verify-pinned-checkbox.button.description', 'Restrict Jenkins Nodes') /} - {param fields: [[ - 'id': 'isVerifyPinned', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), - 'isChecked': $isVerifyPinned - ]] /} - {/call} - {call aui.form.textField} - {param id: 'verifyLabel' /} - {param labelContent: stash_i18n('stash.web.stash.verifyLabel.label', 'Jenkins Node Label') /} - {param value: $verifyLabel /} - {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Jenkins node label to restrict publish builds to') /} - {/call} - {call aui.form.textField} - {param id: 'maxVerifyChain' /} - {param labelContent: stash_i18n('stash.web.stash.maxVerifyChain.label', 'Maximum number of commits to verify on a single push') /} - {param value: $maxVerifyChain /} - {/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.rebuild-on-update-radio.button.description', 'Rebuild PR on target change') /} - {param fields: [[ - 'id': 'rebuildOnUpdate', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), - 'isChecked': $rebuildOnUpdate - ]] /} - {/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.strict-verify-mode.button.description', 'Strict Verify Mode') /} - {param fields: [[ - 'id': 'isStrictVerifyMode', - 'labelText': stash_i18n('stash.web.stash.strict-verify-mode.button.label', 'Enabled'), - 'isChecked': $isStrictVerifyMode - ]] /} - {/call} - {/param} - {/call} - - {/param} -{/call} - -

Artifacts

-{call aui.group.group} - {param content} - - {call aui.group.item} - {param content} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.junit-enabled.button.description', 'Junit Test Results') /} - {param fields: [[ - 'id': 'isJunit', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), - 'isChecked': $isJunit - ]] /} - {/call} - {call aui.form.textField} - {param id: 'junitPath' /} - {param labelContent: stash_i18n('stash.web.stash.junitPath.label', 'Result Location') /} - {param value: $junitPath /} - {param descriptionText: stash_i18n('stash.web.stash.junitPath.description', 'Path to use for junit results (e.g. build/test-results/*.xml)') /} - {/call} - {/param} - {/call} - - - {call aui.group.item} - {param content} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.artifacts-enabled.button.description', 'Artifact Archival') /} - {param fields: [[ - 'id': 'artifactsEnabled', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), - 'isChecked': $artifactsEnabled - ]] /} - {/call} - {call aui.form.textField} - {param id: 'artifactsPath' /} - {param labelContent: stash_i18n('stash.web.stash.artifactsPath.label', 'Artifact Location') /} - {param value: $artifactsPath /} - {param descriptionText: stash_i18n('stash.web.stash.artifactsPath.description', 'Path to use for artifacts (e.g. **/*.log)') /} - {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.enable-publish.button.description', 'Publishes') /} + {param fields: [[ + 'id': 'publishEnabled', + 'labelText': stash_i18n('stash.web.stash.enable-verification.button.label', 'Enabled'), + 'isChecked': $publishEnabled + ]] /} + {/call} + {call aui.form.textField} + {param id: 'publishBranchRegex' /} + {param labelContent: stash_i18n('stash.web.stash.publishBranchRegex.label', 'Trigger Regex') /} + {param value: $publishBranchRegex /} + {param fieldWidth: 'long' /} + {param descriptionText: stash_i18n('stash.web.stash.publishBranchRegex.description', 'Branches that match this regex will have a publish build run on Jenkins. Be aware that this is the full git ref - e.g. "refs/heads/master" or "refs/heads/.*", not just "master".') /} + {param extraAttributes: 'data-aui-notification-field data-aui-notification-info="Branches that match this regex will have a publish build run on Jenkins. Be aware that this is the full git ref - e.g. "refs/heads/master" or "refs/heads/.*", not just "master"."' /} + {/call} + {call aui.form.textField} + {param id: 'publishBuildCommand' /} + {param labelContent: stash_i18n('stash.web.stash.publishBuildCommand.label', 'Command') /} + {param descriptionText: stash_i18n('stash.web.stash.publishBuildCommand.description', 'The command that will be run during a publish build. For example: ./scripts/publish.sh') /} + {param value: $publishBuildCommand /} + {param fieldWidth: 'long' /} + {/call} + {call aui.expander.trigger} + {param id: 'advanced-expand-publish'/} + {param contentId: 'advanced-expand-publish-content'/} + {param tag: 'a'/} + {param content: 'Advanced Publishing'/} + {param replaceText: 'Hide Advanced'/} + {/call} + {call aui.expander.content} + {param id: 'advanced-expand-publish-content'/} + {param content} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.publish-pinned-checkbox.button.description', 'Restrict Jenkins Nodes') /} + {param fields: [[ + 'id': 'isPublishPinned', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $isPublishPinned + ]] /} + {/call} + {call aui.form.textField} + {param id: 'publishLabel' /} + {param labelContent: stash_i18n('stash.web.stash.verifyLabel.label', 'Jenkins Node Label') /} + {param value: $publishLabel /} + {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Jenkins node label to restrict publish builds to') /} + {/call} + {/param} + {/call} {/param} - {/call} - - {/param} -{/call} + {/call} /* Publishing group End */ -

Advanced

+

Verifications

+ {call aui.group.group} + {param content} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.enable-verification.button.description', 'Commit Verifies') /} + {param fields: [[ + 'id': 'verificationEnabled', + 'labelText': stash_i18n('stash.web.stash.enable-verification.button.label', 'Enabled'), + 'isChecked': $verificationEnabled + ]] /} + {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.enable-verify-pr.button.description', 'PR Verifies') /} + {param fields: [[ + 'id': 'verifyPREnabled', + 'labelText': stash_i18n('stash.web.stash.enable-verify-pr.button.label', 'Enabled'), + 'isChecked': $verifyPREnabled + ]] /} + {/call} + {call aui.form.textField} + {param id: 'verifyBranchRegex' /} + {param labelContent: stash_i18n('stash.web.stash.verifyBranchRegex.label', 'Trigger Regex') /} + {param value: $verifyBranchRegex /} + {param fieldWidth: 'long' /} + {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Branches that match this regex will have a verify build run on Jenkins. Be aware that this is the full git ref - e.g. "refs/heads/master" or "refs/heads/.*", not just "master".') /} + {/call} + {call aui.form.textField} + {param id: 'verifyBuildCommand' /} + {param labelContent: stash_i18n('stash.web.stash.verifyBuildCommand.label', 'Command') /} + {param descriptionText: stash_i18n('stash.web.stash.verifyBuildCommand.description', 'The command that will be run during a verification or verify_pr build. For example: ./scripts/verify.sh') /} + {param value: $verifyBuildCommand /} + {param fieldWidth: 'long' /} + {/call} + {call aui.expander.trigger} + {param id: 'advanced-expand-verify'/} + {param contentId: 'advanced-expand-verify-content'/} + {param tag: 'a'/} + {param content: 'Advanced Verification'/} + {param replaceText: 'Hide Advanced'/} + {/call} + {call aui.expander.content} + {param id: 'advanced-expand-verify-content'/} + {param content} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.verify-pinned-checkbox.button.description', 'Restrict Jenkins Nodes') /} + {param fields: [[ + 'id': 'isVerifyPinned', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $isVerifyPinned + ]] /} + {/call} + {call aui.form.textField} + {param id: 'verifyLabel' /} + {param labelContent: stash_i18n('stash.web.stash.verifyLabel.label', 'Jenkins Node Label') /} + {param value: $verifyLabel /} + {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Jenkins node label to restrict publish builds to') /} + {/call} + {call aui.form.textField} + {param id: 'maxVerifyChain' /} + {param labelContent: stash_i18n('stash.web.stash.maxVerifyChain.label', 'Maximum number of commits to verify on a single push') /} + {param value: $maxVerifyChain /} + {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.rebuild-on-update-radio.button.description', 'Rebuild PR on target change') /} + {param fields: [[ + 'id': 'rebuildOnUpdate', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $rebuildOnUpdate + ]] /} + {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.strict-verify-mode.button.description', 'Strict Verify Mode') /} + {param fields: [[ + 'id': 'isStrictVerifyMode', + 'labelText': stash_i18n('stash.web.stash.strict-verify-mode.button.label', 'Enabled'), + 'isChecked': $isStrictVerifyMode + ]] /} + {/call} + {/param} + {/call} /* Expanded Group End */ + {/param} + {/call} /* Verification Group End */ - -{call aui.expander.trigger} - {param id: 'advanced-expand-jenkins'/} - {param contentId: 'advanced-expand-jenkins-content'/} - {param tag: 'a'/} - {param content: 'Show Advanced'/} - {param replaceText: 'Hide Advanced'/} -{/call} -{call aui.expander.content} - {param id: 'advanced-expand-jenkins-content'/} - {param content} - - {call aui.group.group} - {param content} - - {call aui.group.item} - {param content} - {call aui.form.selectField} - {param id: 'jenkinsServerName' /} - {param labelContent: stash_i18n('stash.web.stash.jenkinsServerName.label', 'Jenkins Server') /} - {param options: $jenkinsServersData /} - {param value: $jenkinsServerName /} - {/call} - {/param} - {/call} - - {call aui.group.item} - {param content} - {call aui.form.textField} - {param id: 'buildTimeout' /} - {param labelContent: stash_i18n('stash.web.stash.buildTimeout.label', 'Build Timeout (minutes)') /} - {param descriptionText: stash_i18n('stash.web.stash.buildTimeout.description', 'Timeout (in minutes) for all builds. A value of -1 uses Jenkins-wide default. Minimum value of ' + $buildTimeoutMin + ' and maximum value of ' + $buildTimeoutMax + '.') /} - {param value: $buildTimeout /} - {param fieldWidth: 'short' /} - {/call} - {/param} - {/call} - - {/param} - {/call} - -{call aui.group.group} - {param content} -

 

- {/param} -{/call} - +

Artifacts

{call aui.group.group} - {param content} + {param content} - {call aui.group.item} - {param content} - {call aui.form.textField} - {param id: 'prebuildCommand' /} - {param descriptionText: stash_i18n('stash.web.stash.prebuildCommand.description', 'Command to run before all builds (and for PR verifies, before the merge)') /} - {param labelContent: stash_i18n('stash.web.stash.prebuildCommand.label', 'Pre Build Command') /} - {param value: $prebuildCommand /} - {/call} + {call aui.group.item} + {param content} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.junit-enabled.button.description', 'Junit Test Results') /} + {param fields: [[ + 'id': 'isJunit', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $isJunit + ]] /} + {/call} + {call aui.form.textField} + {param id: 'junitPath' /} + {param labelContent: stash_i18n('stash.web.stash.junitPath.label', 'Result Location') /} + {param value: $junitPath /} + {param descriptionContent} + Location of JUnit xml files after a build completes. This is an ant-style glob. + For example, Gradle by default will put results at **/build/test-results/TEST-*.xml. If no xml files are found, the build will fail. This applies + to verify and publish builds. + {/param} + {/call} + {/param} + {/call} /* JUnit Item End */ + + {call aui.group.item} + {param content} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.artifacts-enabled.button.description', 'Save Artifacts') /} + {param fields: [[ + 'id': 'artifactsEnabled', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $artifactsEnabled + ]] /} + {/call} + {call aui.form.textField} + {param id: 'artifactsPath' /} + {param labelContent: stash_i18n('stash.web.stash.artifactsPath.label', 'Artifact Pattern') /} + {param value: $artifactsPath /} + {param descriptionContent} + Comma-delimited ant-style globs you wish + Jenkins to save after a build (of any type) completes. This is useful for picking up logs, coverage reports, etc. + {/param} + {/call} + {/param} + {/call} /* Artifacting Item End */ {/param} {/call} - {call aui.group.item} - {param content} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.preserveJenkinsJobConfig.label', 'Preserve Jenkins Job Config') /} - {param descriptionText: stash_i18n('stash.web.stash.preserveJenkinsJobConfig.description', 'See below for details.') /} - {param fields: [[ - 'id': 'isPreserveJenkinsJobConfig', - 'labelText': stash_i18n('stash.web.stash.preserveJenkinsJobConfig.button.label', 'Enabled'), - 'isChecked': $isPreserveJenkinsJobConfig - ]] /} - {/call} - {/param} +

Advanced

+ {call aui.expander.trigger} + {param id: 'advanced-expand-jenkins'/} + {param contentId: 'advanced-expand-jenkins-content'/} + {param tag: 'a'/} + {param content: 'Show Advanced'/} + {param replaceText: 'Hide Advanced'/} {/call} - - {/param} + {call aui.expander.content} + {param id: 'advanced-expand-jenkins-content'/} + {param content} + + {call aui.group.group} + {param content} + {call aui.group.item} + {param content} + {call aui.form.selectField} + {param id: 'jenkinsServerName' /} + {param labelContent: stash_i18n('stash.web.stash.jenkinsServerName.label', 'Jenkins Server') /} + {param options: $jenkinsServersData /} + {param value: $jenkinsServerName /} + {/call} + {/param} + {/call} + + {call aui.group.item} + {param content} + {call aui.form.textField} + {param id: 'buildTimeout' /} + {param labelContent: stash_i18n('stash.web.stash.buildTimeout.label', 'Build Timeout (minutes)') /} + {param descriptionText: stash_i18n('stash.web.stash.buildTimeout.description', 'Timeout (in minutes) for all builds. A value of -1 uses Jenkins-wide default. Minimum value of ' + $buildTimeoutMin + ' and maximum value of ' + $buildTimeoutMax + '.') /} + {param value: $buildTimeout /} + {param fieldWidth: 'short' /} + {/call} + {/param} + {/call} + {/param} + {/call} /* Group End */ + + {call aui.group.group} + {param content} +

 

+ {/param} + {/call} + + {call aui.group.group} + {param content} + + {call aui.group.item} + {param content} + {call aui.form.textField} + {param id: 'prebuildCommand' /} + {param descriptionText: stash_i18n('stash.web.stash.prebuildCommand.description', 'Command to run before all builds (and for PR verifies, before the merge)') /} + {param labelContent: stash_i18n('stash.web.stash.prebuildCommand.label', 'Pre Build Command') /} + {param value: $prebuildCommand /} + {/call} + {/param} + {/call} + + {call aui.group.item} + {param content} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.preserveJenkinsJobConfig.label', 'Preserve Jenkins Job Config') /} + {param descriptionText: stash_i18n('stash.web.stash.preserveJenkinsJobConfig.description', 'Enabling this option stops Stashbot from setting any command, artifact, testing, etc. options for your build.') /} + {param fields: [[ + 'id': 'isPreserveJenkinsJobConfig', + 'labelText': stash_i18n('stash.web.stash.preserveJenkinsJobConfig.button.label', 'Enabled'), + 'isChecked': $isPreserveJenkinsJobConfig + ]] /} + {/call} + {/param} + {/call} + {/param} + {/call} /* Group End */ + + {call aui.group.group} + {param content} +

 

+ {/param} + {/call} + + {call aui.group.group} + {param content} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.email-enabled.button.description', 'E-mail notifications') /} + {param fields: [[ + 'id': 'isEmailNotificationsEnabled', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $isEmailNotificationsEnabled + ]] /} + {/call} + {call aui.form.textField} + {param id: 'emailRecipients' /} + {param labelContent: stash_i18n('stash.web.stash.emailRecipients.label', 'E-mail Recipients') /} + {param value: $emailRecipients /} + {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.emailForEveryUnstableBuild.button.description', 'E-mail Options') /} + {param fields: [[ + 'id': 'isEmailForEveryUnstableBuild', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Send e-mail for every unstable build'), + 'isChecked': $isEmailForEveryUnstableBuild + ]] /} + {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.emailSendToIndividuals.button.description', '') /} + {param fields: [[ + 'id': 'isEmailSendToIndividuals', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Send separate e-mails to individuals who broke the build'), + 'isChecked': $isEmailSendToIndividuals + ]] /} + {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.emailPerModuleEmail.button.description', '') /} + {param fields: [[ + 'id': 'isEmailPerModuleEmail', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Send e-mail for each failed module'), + 'isChecked': $isEmailPerModuleEmail + ]] /} + {/call} + {/param} + {/call} /* Group End */ + {/param} {/call} -{call aui.group.group} - {param content} -

 

- {/param} -{/call} - {call aui.group.group} +

 

+ {call aui.form.buttons} {param content} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.email-enabled.button.description', 'E-mail notifications') /} - {param fields: [[ - 'id': 'isEmailNotificationsEnabled', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), - 'isChecked': $isEmailNotificationsEnabled - ]] /} - {/call} - {call aui.form.textField} - {param id: 'emailRecipients' /} - {param labelContent: stash_i18n('stash.web.stash.emailRecipients.label', 'E-mail Recipients') /} - {param value: $emailRecipients /} - {/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.emailForEveryUnstableBuild.button.description', 'E-mail Options') /} - {param fields: [[ - 'id': 'isEmailForEveryUnstableBuild', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Send e-mail for every unstable build'), - 'isChecked': $isEmailForEveryUnstableBuild - ]] /} - {/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.emailSendToIndividuals.button.description', '') /} - {param fields: [[ - 'id': 'isEmailSendToIndividuals', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Send separate e-mails to individuals who broke the build'), - 'isChecked': $isEmailSendToIndividuals - ]] /} - {/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.emailPerModuleEmail.button.description', '') /} - {param fields: [[ - 'id': 'isEmailPerModuleEmail', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Send e-mail for each failed module'), - 'isChecked': $isEmailPerModuleEmail - ]] /} - {/call} + {call aui.form.submit} + {param id: 'submit' /} + {param text: stash_i18n('stash.web.stash.ci-prefs.submit', 'Save') /} + {param type: 'submit' /} + {/call} {/param} {/call} - {/param} -{/call} - - -

 

- -{call aui.form.buttons} - {param content} - {call aui.form.submit} - {param id: 'submit' /} - {param text: stash_i18n('stash.web.stash.ci-prefs.submit', 'Save') /} - {param type: 'submit' /} - {/call} - {/param} -{/call} - - - - {/param} + {/param} {/call}

-{call aui.expander.trigger} - {param id: 'advanced-expand-documentation'/} - {param contentId: 'advanced-expand-documentation-content'/} - {param tag: 'a'/} - {param content: 'Show Documentation'/} - {param replaceText: 'Hide Documentation'/} -{/call} + {call aui.expander.trigger} + {param id: 'advanced-expand-documentation'/} + {param contentId: 'advanced-expand-documentation-content'/} + {param tag: 'a'/} + {param content: 'Show Documentation'/} + {param replaceText: 'Hide Documentation'/} + {/call}

{call aui.expander.content} - {param id: 'advanced-expand-documentation-content'/} - {param content} -
-

Regular Expressions:

-

-Regular expressions are simple Java style regular expressions. Extra escaping is not required, and the regular expressions are anchored by default so you must add a trailing ".*" if you want prefix matching. -
-Examples: -

refs/heads/master
-
refs/heads/.*
-
refs/heads/features/.*-verify
-

-

Verify Versus Publish:

-

-When a new change is added to a reference which matches both publish and verify regular expressions, the publish build is triggered first. A verify build is never done on a sha1 which has already triggered a publish build. Also, publish builds are always only triggered on the most recent change to a ref, so if a chain of commits are pushed, only the latest is published (though depending on other settings, multiple verify builds could be triggered, see next section). -

-

Build Pinning

-

-If your jenkins instance has applied labels to build nodes, you can have verifiy or publish builds pinned to a specific build node label by checking these checkboxes, and entering the desired label. -

-

Max Verify Chain:

-

-The max verify chain setting specifies the number of verify builds to trigger due to a single ref update, at most. For example, if the setting is set to 5, and a reference is updated from pointing to commit A, to pointing to commit B, and commit B has commit A as it's parent's parent's parent's parent, that is a chain of five commits have been pushed each dependent on the next, then 5 verify builds will be triggered, one for each commit. If the setting was instead set to 3, only the 3 "most recent" commits would be verified, and 2 would be left unbuilt (but verifies could be triggered manually). If the setting was 0, an unlimited number of builds could be triggered (though still limited by the jenkins configuration setting as well, which puts a cap on it). -

-

Strict Verify Mode

-

-In strict mode, stashbot requires that every commit in a pull request has at least one successful build before it can be merged. This lets you enforce a higher level of verification so that git bisects are reliable. Another example where you might want to do this is if your build acts upon changed pieces only, and can therefore only guarantee consistency if each commit's parent has also been verified. - -Note that if your verify regex does not match a feature branch, but does match an integration branch, and you push to your feature branch then try to merge into your integration branch, there will be missing verify builds. Therefore, if you want stashbot to perform these builds and operate in strict mode, you probably want your verify regex to cover all branches to which you directly push. - -Strict mode is disabled by default, and when enabled it cannot be overridden with the override flag due to implementation details (but users may still perform the merge locally and directly push it, if they have adequate permissions to do so). -

-

Recommendations and Best Practices

-

-If one is releasing using something like git flow, you will probably want to have a release branch which you tag for publishes. This is frequently called refs/heads/release or refs/heads/master. One might then verify refs/heads/.* if one wants verification builds on all branches, or one might prefer to have "free-reign" branches and only perform verification on certain named branches (by doing something like refs/heads/features/.* for verification but allow people to also push to branches called refs/heads/sandbox/.* without verification builds). -

-

-Note also that the configuration should take into account capacity concerns. If the jenkins instance has 10 executors, the typical build takes 10 minutes, and 5-10 changes are pushed per hour, one can probably safely build every commit. If, however, one has 2 executors and each build takes an hour, it will probably result in large latencies if one tries to build all commits, should someone push a chain of 10 commits even once or twice a day. For details, consult your site-specific documentation (your infrastructure guys have established guidelines, right? =) ) -

-

Preserve Jenkins Job Config

-

-When you click save, normally, the job is destroyed and recreated. This option disables that, so editing this page still changes internal state but no longer effects a job (that already exists) at all. Some featuers will work, some will not, but changes made directly in jenkins will no longer be destroyed. -

-
-{/param} + {param id: 'advanced-expand-documentation-content'/} + {param content} +
+

Regular Expressions:

+

+ Regular expressions are simple Java style regular expressions. Extra escaping is not required, and the regular expressions are anchored by default so you must add a trailing ".*" if you want prefix matching. +
+ Examples: +

refs/heads/master
+
refs/heads/.*
+
refs/heads/features/.*-verify
+

+

Verify Versus Publish:

+

+ When a new change is added to a reference which matches both publish and verify regular expressions, the publish build is triggered first. A verify build is never done on a sha1 which has already triggered a publish build. Also, publish builds are always only triggered on the most recent change to a ref, so if a chain of commits are pushed, only the latest is published (though depending on other settings, multiple verify builds could be triggered, see next section). +

+

Build Pinning

+

+ If your jenkins instance has applied labels to build nodes, you can have verifiy or publish builds pinned to a specific build node label by checking these checkboxes, and entering the desired label. +

+

Max Verify Chain:

+

+ The max verify chain setting specifies the number of verify builds to trigger due to a single ref update, at most. For example, if the setting is set to 5, and a reference is updated from pointing to commit A, to pointing to commit B, and commit B has commit A as it's parent's parent's parent's parent, that is a chain of five commits have been pushed each dependent on the next, then 5 verify builds will be triggered, one for each commit. If the setting was instead set to 3, only the 3 "most recent" commits would be verified, and 2 would be left unbuilt (but verifies could be triggered manually). If the setting was 0, an unlimited number of builds could be triggered (though still limited by the jenkins configuration setting as well, which puts a cap on it). +

+

Strict Verify Mode

+

+ In strict mode, stashbot requires that every commit in a pull request has at least one successful build before it can be merged. This lets you enforce a higher level of verification so that git bisects are reliable. Another example where you might want to do this is if your build acts upon changed pieces only, and can therefore only guarantee consistency if each commit's parent has also been verified. + + Note that if your verify regex does not match a feature branch, but does match an integration branch, and you push to your feature branch then try to merge into your integration branch, there will be missing verify builds. Therefore, if you want stashbot to perform these builds and operate in strict mode, you probably want your verify regex to cover all branches to which you directly push. + + Strict mode is disabled by default, and when enabled it cannot be overridden with the override flag due to implementation details (but users may still perform the merge locally and directly push it, if they have adequate permissions to do so). +

+

Recommendations and Best Practices

+

+ If one is releasing using something like git flow, you will probably want to have a release branch which you tag for publishes. This is frequently called refs/heads/release or refs/heads/master. One might then verify refs/heads/.* if one wants verification builds on all branches, or one might prefer to have "free-reign" branches and only perform verification on certain named branches (by doing something like refs/heads/features/.* for verification but allow people to also push to branches called refs/heads/sandbox/.* without verification builds). +

+

+ Note also that the configuration should take into account capacity concerns. If the jenkins instance has 10 executors, the typical build takes 10 minutes, and 5-10 changes are pushed per hour, one can probably safely build every commit. If, however, one has 2 executors and each build takes an hour, it will probably result in large latencies if one tries to build all commits, should someone push a chain of 10 commits even once or twice a day. For details, consult your site-specific documentation (your infrastructure guys have established guidelines, right? =) ) +

+

Preserve Jenkins Job Config

+

+ When you click save, normally, the job is destroyed and recreated. This option disables that, so editing this page still changes internal state but no longer effects a job (that already exists) at all. Some featuers will work, some will not, but changes made directly in jenkins will no longer be destroyed. +

+
+ {/param} {/call} + {/template} From 5475affd6283270eee2481a23f69e8988afff624 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Thu, 19 Nov 2015 16:58:18 -0800 Subject: [PATCH 37/54] Rewrite documentation with some examples -First draft --- .../static/repository-configuration-panel.soy | 134 ++++++++++++------ 1 file changed, 94 insertions(+), 40 deletions(-) diff --git a/src/main/resources/static/repository-configuration-panel.soy b/src/main/resources/static/repository-configuration-panel.soy index c7f1658..21bf099 100644 --- a/src/main/resources/static/repository-configuration-panel.soy +++ b/src/main/resources/static/repository-configuration-panel.soy @@ -425,46 +425,100 @@ {param id: 'advanced-expand-documentation-content'/} {param content}
-

Regular Expressions:

-

- Regular expressions are simple Java style regular expressions. Extra escaping is not required, and the regular expressions are anchored by default so you must add a trailing ".*" if you want prefix matching. -
- Examples: -

refs/heads/master
-
refs/heads/.*
-
refs/heads/features/.*-verify
-

-

Verify Versus Publish:

-

- When a new change is added to a reference which matches both publish and verify regular expressions, the publish build is triggered first. A verify build is never done on a sha1 which has already triggered a publish build. Also, publish builds are always only triggered on the most recent change to a ref, so if a chain of commits are pushed, only the latest is published (though depending on other settings, multiple verify builds could be triggered, see next section). -

-

Build Pinning

-

- If your jenkins instance has applied labels to build nodes, you can have verifiy or publish builds pinned to a specific build node label by checking these checkboxes, and entering the desired label. -

-

Max Verify Chain:

-

- The max verify chain setting specifies the number of verify builds to trigger due to a single ref update, at most. For example, if the setting is set to 5, and a reference is updated from pointing to commit A, to pointing to commit B, and commit B has commit A as it's parent's parent's parent's parent, that is a chain of five commits have been pushed each dependent on the next, then 5 verify builds will be triggered, one for each commit. If the setting was instead set to 3, only the 3 "most recent" commits would be verified, and 2 would be left unbuilt (but verifies could be triggered manually). If the setting was 0, an unlimited number of builds could be triggered (though still limited by the jenkins configuration setting as well, which puts a cap on it). -

-

Strict Verify Mode

-

- In strict mode, stashbot requires that every commit in a pull request has at least one successful build before it can be merged. This lets you enforce a higher level of verification so that git bisects are reliable. Another example where you might want to do this is if your build acts upon changed pieces only, and can therefore only guarantee consistency if each commit's parent has also been verified. - - Note that if your verify regex does not match a feature branch, but does match an integration branch, and you push to your feature branch then try to merge into your integration branch, there will be missing verify builds. Therefore, if you want stashbot to perform these builds and operate in strict mode, you probably want your verify regex to cover all branches to which you directly push. - - Strict mode is disabled by default, and when enabled it cannot be overridden with the override flag due to implementation details (but users may still perform the merge locally and directly push it, if they have adequate permissions to do so). -

-

Recommendations and Best Practices

-

- If one is releasing using something like git flow, you will probably want to have a release branch which you tag for publishes. This is frequently called refs/heads/release or refs/heads/master. One might then verify refs/heads/.* if one wants verification builds on all branches, or one might prefer to have "free-reign" branches and only perform verification on certain named branches (by doing something like refs/heads/features/.* for verification but allow people to also push to branches called refs/heads/sandbox/.* without verification builds). -

-

- Note also that the configuration should take into account capacity concerns. If the jenkins instance has 10 executors, the typical build takes 10 minutes, and 5-10 changes are pushed per hour, one can probably safely build every commit. If, however, one has 2 executors and each build takes an hour, it will probably result in large latencies if one tries to build all commits, should someone push a chain of 10 commits even once or twice a day. For details, consult your site-specific documentation (your infrastructure guys have established guidelines, right? =) ) -

-

Preserve Jenkins Job Config

-

- When you click save, normally, the job is destroyed and recreated. This option disables that, so editing this page still changes internal state but no longer effects a job (that already exists) at all. Some featuers will work, some will not, but changes made directly in jenkins will no longer be destroyed. -

+
+

Build/Job Types

+
    +
  • + Verify +
      +
    • verification – Each commit in a PR gets verified by its own run of the verification job
    • +
    • verify_pr – The PR gets tested in verify_pr. The difference between this and verification is + that the PR gets merged into the target branch. +
    • +
    +
  • +
  • + Publish +
      +
    • publish – The publish build is useful for publishing artifacts, releases, etc. It is expected + that the command you run for a publish will take care of the actual publishing of artifacts. In case you push + a commit to a branch that matches both the verify and publish regular expressions, the publish job will take + prescendence over the verification build. The commit will not be verified. +
    • +
    +
  • +
+
+
 
+
+

Branch Regular Expressions

+ The branch regex specify when a verification/verify_pr build will start and when a publish build will start. It requires + a full git reference and uses Java for matching regexes. +

Examples

+
+
refs/heads/master
+
Matches just the master branch.
+ +
refs/heads/.*
+
Matches all branches.
+ +
refs/heads/(master|develop)
+
Matches the master or develop branch
+ +
refs/(heads/master|tags/.*)
+
Matches the master branch and all tags
+
+

Publish Examples

+
refs/heads/(master|develop)
+
Publish on any commit pushed or PR merged into master or develop branch.
+ +
refs/(heads/master|tags/.*)
+
Any commit pushed or PR merged into master will cause a publish job to start. In addition, + a tag can be pushed independently and it will also start a publish job. This allows you to publish master + almost like a testable snapshot, and when you're happy, you can push a tag separately and actually create + a release.
+ +
refs/heads/(master|release/.*)
+
Publish commits/PRs to master or any branch starting with release/ such as release/2.4.0 +
+
+
+
 
+
+

Advanced

+ +

Build Pinning

+ If your jenkins instance has applied labels to build nodes, you can have verifiy or publish builds pinned to a specific build + node label by checking these checkboxes, and entering the desired label. + +

Build Timeouts

+ All Jenkins jobs created by Stashbot have a timeout set on them. By default, the jobs use the default timeout set by the admin + of Stash. If you’d like a different value, you can override it at the repository level. A value of -1 uses the Jenkins-wide + default. The allowable range is from 5 minutes to 1 week (10080 minutes). + +

Max Verify Chain

+ The max verify chain setting specifies the number of verification builds to trigger due to a single ref update, at most. For + example, if the setting is set to 5, and a reference is updated from pointing to commit A, to pointing to commit B, and commit B + has commit A as it's parent's parent's parent's parent, that is a chain of five commits have been pushed each dependent on the next, + then 5 verify builds will be triggered, one for each commit. If the setting was instead set to 3, only the 3 "most recent" commits + would be verified, and 2 would be left unbuilt (but verifies could be triggered manually). If the setting was 0, an unlimited number + of builds could be triggered (though still limited by the jenkins configuration setting as well, which puts a cap on it). + +

Strict Verify Mode

+ In strict mode, stashbot requires that every commit in a pull request has at least one successful build before it can be merged. + This lets you enforce a higher level of verification so that git bisects are reliable. Another example where you might want to do + this is if your build acts upon changed pieces only, and can therefore only guarantee consistency if each commit's parent has also + been verified. Note that if your verify regex does not match a feature branch, but does match an integration branch, and you push + to your feature branch then try to merge into your integration branch, there will be missing verify builds. Therefore, if you want + stashbot to perform these builds and operate in strict mode, you probably want your verify regex to cover all branches to which you + directly push. Strict mode is disabled by default, and when enabled it cannot be overridden with the override flag due to + implementation details (but users may still perform the merge locally and directly push it, if they have adequate permissions to do so). + +

Preserve Jenkins Job Config

+ When you click save, normally, the job is destroyed and recreated. This option disables that, so editing this page still changes internal + state but no longer effects a job (that already exists) at all. Some features will work, some will not, but changes made directly in + jenkins will no longer be destroyed. +
{/param} {/call} From 71d59ae0467b91e3a5b2a5c30df5bd98f2b9af35 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Thu, 19 Nov 2015 20:07:42 -0800 Subject: [PATCH 38/54] Allow for validation + notifications -All our text boxes have to be 1-255 characters -Give examples in our tooltips and remove some examples from descriptions --- src/main/resources/atlassian-plugin.xml | 1 + src/main/resources/css/stashbot.css | 3 ++ src/main/resources/js/stashbot.js | 6 +++ .../static/repository-configuration-panel.soy | 37 ++++++++++++++++--- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/main/resources/atlassian-plugin.xml b/src/main/resources/atlassian-plugin.xml index 1d57946..3941a3a 100644 --- a/src/main/resources/atlassian-plugin.xml +++ b/src/main/resources/atlassian-plugin.xml @@ -17,6 +17,7 @@ com.atlassian.auiplugin:aui-experimental-iconfont com.atlassian.auiplugin:aui-experimental-tooltips com.atlassian.auiplugin:aui-form-notification + com.atlassian.auiplugin:aui-form-validation diff --git a/src/main/resources/css/stashbot.css b/src/main/resources/css/stashbot.css index b6c4a4f..a10d230 100644 --- a/src/main/resources/css/stashbot.css +++ b/src/main/resources/css/stashbot.css @@ -3,3 +3,6 @@ input.palantir-stashbot-disabled, span.palantir-stashbot-disabled { color:#999999; } +dt { + font-family: monospaced; +} diff --git a/src/main/resources/js/stashbot.js b/src/main/resources/js/stashbot.js index 6d35830..2bdec5c 100644 --- a/src/main/resources/js/stashbot.js +++ b/src/main/resources/js/stashbot.js @@ -1,7 +1,13 @@ +require(['aui/form-notification']); +require(['aui/form-validation']); require(['jquery'], function($) { console.debug("Injecting JS to disable dropdown") $(window).load(function() { + + // Activate tooltip on any item with this class + AJS.$(".tooltip-stashbot-class").tooltip(); + console.debug("Detecting if jenkins server config is locked or not") locked = $("#isJenkinsServerLocked") if (locked.text() == "locked") { diff --git a/src/main/resources/static/repository-configuration-panel.soy b/src/main/resources/static/repository-configuration-panel.soy index 21bf099..1df6fe4 100644 --- a/src/main/resources/static/repository-configuration-panel.soy +++ b/src/main/resources/static/repository-configuration-panel.soy @@ -64,6 +64,7 @@ {/param} {/call} {/if} + {call aui.form.form} {param action: '' /} {param content} @@ -97,15 +98,22 @@ {param labelContent: stash_i18n('stash.web.stash.publishBranchRegex.label', 'Trigger Regex') /} {param value: $publishBranchRegex /} {param fieldWidth: 'long' /} - {param descriptionText: stash_i18n('stash.web.stash.publishBranchRegex.description', 'Branches that match this regex will have a publish build run on Jenkins. Be aware that this is the full git ref - e.g. "refs/heads/master" or "refs/heads/.*", not just "master".') /} - {param extraAttributes: 'data-aui-notification-field data-aui-notification-info="Branches that match this regex will have a publish build run on Jenkins. Be aware that this is the full git ref - e.g. "refs/heads/master" or "refs/heads/.*", not just "master"."' /} + {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} + {param descriptionContent} + Branches that match this regex will have a publish build run on Jenkins. Be aware that this is + the full git ref - + e.g. "refs/heads/master" or "refs/heads/.*", not just "master". + {/param} + {param infoMessage: 'Examples:
  • refs/heads/master
  • refs/heads/.*
  • refs/tags/.*
  • refs/heads/(master|develop)
' /} {/call} {call aui.form.textField} {param id: 'publishBuildCommand' /} {param labelContent: stash_i18n('stash.web.stash.publishBuildCommand.label', 'Command') /} - {param descriptionText: stash_i18n('stash.web.stash.publishBuildCommand.description', 'The command that will be run during a publish build. For example: ./scripts/publish.sh') /} + {param descriptionText: stash_i18n('stash.web.stash.publishBuildCommand.description', 'The command that will be run during a publish build.') /} + {param infoMessage: 'Examples:
  • ./scripts/publish.sh
  • ENVVAR=1 ENVVAR=2 ./scripts/publish.sh
  • ./stashbot/publish.sh
' /} {param value: $publishBuildCommand /} {param fieldWidth: 'long' /} + {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} {/call} {call aui.expander.trigger} {param id: 'advanced-expand-publish'/} @@ -161,12 +169,20 @@ {param labelContent: stash_i18n('stash.web.stash.verifyBranchRegex.label', 'Trigger Regex') /} {param value: $verifyBranchRegex /} {param fieldWidth: 'long' /} - {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Branches that match this regex will have a verify build run on Jenkins. Be aware that this is the full git ref - e.g. "refs/heads/master" or "refs/heads/.*", not just "master".') /} + {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} + {param descriptionContent} + Branches that match this regex will have a verify build run on Jenkins. Be aware that this is + the full git ref - + e.g. "refs/heads/master" or "refs/heads/.*", not just "master". + {/param} + {param infoMessage: 'Examples:
  • refs/heads/master
  • refs/heads/.*
  • refs/heads/(master|develop)
  • refs/heads/feature/.*
' /} {/call} {call aui.form.textField} {param id: 'verifyBuildCommand' /} {param labelContent: stash_i18n('stash.web.stash.verifyBuildCommand.label', 'Command') /} - {param descriptionText: stash_i18n('stash.web.stash.verifyBuildCommand.description', 'The command that will be run during a verification or verify_pr build. For example: ./scripts/verify.sh') /} + {param descriptionText: stash_i18n('stash.web.stash.verifyBuildCommand.description', 'The command that will be run during a verification or verify_pr build.') /} + {param infoMessage: 'Examples:
  • ./scripts/verify.sh
  • ENVVAR=1 ENVVAR=2 ./scripts/verifytest.sh
  • ./stashbot/check.sh
' /} + {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} {param value: $verifyBuildCommand /} {param fieldWidth: 'long' /} {/call} @@ -193,11 +209,13 @@ {param labelContent: stash_i18n('stash.web.stash.verifyLabel.label', 'Jenkins Node Label') /} {param value: $verifyLabel /} {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Jenkins node label to restrict publish builds to') /} + {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} {/call} {call aui.form.textField} {param id: 'maxVerifyChain' /} {param labelContent: stash_i18n('stash.web.stash.maxVerifyChain.label', 'Maximum number of commits to verify on a single push') /} {param value: $maxVerifyChain /} + {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} {/call} {call aui.form.checkboxField} {param legendContent: stash_i18n('stash.web.stash.rebuild-on-update-radio.button.description', 'Rebuild PR on target change') /} @@ -243,6 +261,8 @@ For example, Gradle by default will put results at **/build/test-results/TEST-*.xml. If no xml files are found, the build will fail. This applies to verify and publish builds. {/param} + {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} + {param infoMessage: 'Examples:
  • **/build/test-results/TEST-*.xml
  • **/build/test-results/TEST-*.xml,testserver/tests/*.xml
' /} {/call} {/param} {/call} /* JUnit Item End */ @@ -265,6 +285,8 @@ Comma-delimited ant-style globs you wish Jenkins to save after a build (of any type) completes. This is useful for picking up logs, coverage reports, etc. {/param} + {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} + {param infoMessage: 'Examples:
  • **/logs/*.log
  • **/testserver/logs/*.log,npm-debug.log,build/**/*.log
' /} {/call} {/param} {/call} /* Artifacting Item End */ @@ -305,6 +327,7 @@ {param descriptionText: stash_i18n('stash.web.stash.buildTimeout.description', 'Timeout (in minutes) for all builds. A value of -1 uses Jenkins-wide default. Minimum value of ' + $buildTimeoutMin + ' and maximum value of ' + $buildTimeoutMax + '.') /} {param value: $buildTimeout /} {param fieldWidth: 'short' /} + {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} {/call} {/param} {/call} @@ -327,6 +350,7 @@ {param descriptionText: stash_i18n('stash.web.stash.prebuildCommand.description', 'Command to run before all builds (and for PR verifies, before the merge)') /} {param labelContent: stash_i18n('stash.web.stash.prebuildCommand.label', 'Pre Build Command') /} {param value: $prebuildCommand /} + {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} {/call} {/param} {/call} @@ -367,6 +391,7 @@ {param id: 'emailRecipients' /} {param labelContent: stash_i18n('stash.web.stash.emailRecipients.label', 'E-mail Recipients') /} {param value: $emailRecipients /} + {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} {/call} {call aui.form.checkboxField} {param legendContent: stash_i18n('stash.web.stash.emailForEveryUnstableBuild.button.description', 'E-mail Options') /} @@ -512,7 +537,7 @@ to your feature branch then try to merge into your integration branch, there will be missing verify builds. Therefore, if you want stashbot to perform these builds and operate in strict mode, you probably want your verify regex to cover all branches to which you directly push. Strict mode is disabled by default, and when enabled it cannot be overridden with the override flag due to - implementation details (but users may still perform the merge locally and directly push it, if they have adequate permissions to do so). + implementation details (but users may still perform the merge locally and directly push it, if they have adequate permissions to do so).

Preserve Jenkins Job Config

When you click save, normally, the job is destroyed and recreated. This option disables that, so editing this page still changes internal From 466d4cf53e4fe4a19a584af05e6e93e2003b43bd Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Mon, 23 Nov 2015 11:37:39 -0800 Subject: [PATCH 39/54] Set some decent defaults for regexes and commands to run --- .../stashbot/persistence/RepositoryConfiguration.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/persistence/RepositoryConfiguration.java b/src/main/java/com/palantir/stash/stashbot/persistence/RepositoryConfiguration.java index b05b33d..229839e 100644 --- a/src/main/java/com/palantir/stash/stashbot/persistence/RepositoryConfiguration.java +++ b/src/main/java/com/palantir/stash/stashbot/persistence/RepositoryConfiguration.java @@ -37,25 +37,25 @@ public interface RepositoryConfiguration extends Entity { public void setCiEnabled(Boolean url); @NotNull - @Default("empty") + @Default("refs/(heads/master|tags/.*)") public String getPublishBranchRegex(); public void setPublishBranchRegex(String publishBranchRegex); @NotNull - @Default("/bin/true") + @Default("./scripts/publish.sh") public String getPublishBuildCommand(); public void setPublishBuildCommand(String publishBuildCommand); @NotNull - @Default("empty") + @Default("refs/heads/.*") public String getVerifyBranchRegex(); public void setVerifyBranchRegex(String verifyBranchRegex); @NotNull - @Default("/bin/true") + @Default("./scripts/verify.sh") public String getVerifyBuildCommand(); public void setVerifyBuildCommand(String verifyBuildCommand); From eb99047d54ed1c8f7e388194b984afd74a8d739a Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Tue, 24 Nov 2015 19:26:18 -0800 Subject: [PATCH 40/54] Add a beginner's dialog box 'What is stashbot?' -Just a basic what is -I embeded the SVG diagrams because atlassian docs aren't clear about image import --- src/main/resources/atlassian-plugin.xml | 1 + src/main/resources/js/stashbot.js | 10 +++++ .../static/repository-configuration-panel.soy | 40 +++++++++++++++++-- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/main/resources/atlassian-plugin.xml b/src/main/resources/atlassian-plugin.xml index 3941a3a..0ad4ed8 100644 --- a/src/main/resources/atlassian-plugin.xml +++ b/src/main/resources/atlassian-plugin.xml @@ -18,6 +18,7 @@ com.atlassian.auiplugin:aui-experimental-tooltips com.atlassian.auiplugin:aui-form-notification com.atlassian.auiplugin:aui-form-validation + com.atlassian.auiplugin:dialog2 diff --git a/src/main/resources/js/stashbot.js b/src/main/resources/js/stashbot.js index 2bdec5c..e75d5aa 100644 --- a/src/main/resources/js/stashbot.js +++ b/src/main/resources/js/stashbot.js @@ -8,6 +8,16 @@ require(['jquery'], function($) { // Activate tooltip on any item with this class AJS.$(".tooltip-stashbot-class").tooltip(); + AJS.$("#stashbot-howto-button").click(function(e) { + e.preventDefault(); + AJS.dialog2("#stashbot-howto-dialog").show(); + }); + + AJS.$("#stashbot-howto-dialog-close-button").click(function(e) { + e.preventDefault(); + AJS.dialog2("#stashbot-howto-dialog").hide() + }); + console.debug("Detecting if jenkins server config is locked or not") locked = $("#isJenkinsServerLocked") if (locked.text() == "locked") { diff --git a/src/main/resources/static/repository-configuration-panel.soy b/src/main/resources/static/repository-configuration-panel.soy index 1df6fe4..85aa77a 100644 --- a/src/main/resources/static/repository-configuration-panel.soy +++ b/src/main/resources/static/repository-configuration-panel.soy @@ -79,6 +79,9 @@ 'isChecked': $ciEnabled ]] /} {/call} + + What is Stashbot? + {/param} {/call} /* Top group end */ @@ -109,7 +112,7 @@ {call aui.form.textField} {param id: 'publishBuildCommand' /} {param labelContent: stash_i18n('stash.web.stash.publishBuildCommand.label', 'Command') /} - {param descriptionText: stash_i18n('stash.web.stash.publishBuildCommand.description', 'The command that will be run during a publish build.') /} + {param descriptionText: stash_i18n('stash.web.stash.publishBuildCommand.description', 'The command that will be run during a publish build. This should be a script that runs a publishing command like ./gradlew publish') /} {param infoMessage: 'Examples:
  • ./scripts/publish.sh
  • ENVVAR=1 ENVVAR=2 ./scripts/publish.sh
  • ./stashbot/publish.sh
' /} {param value: $publishBuildCommand /} {param fieldWidth: 'long' /} @@ -180,7 +183,7 @@ {call aui.form.textField} {param id: 'verifyBuildCommand' /} {param labelContent: stash_i18n('stash.web.stash.verifyBuildCommand.label', 'Command') /} - {param descriptionText: stash_i18n('stash.web.stash.verifyBuildCommand.description', 'The command that will be run during a verification or verify_pr build.') /} + {param descriptionText: stash_i18n('stash.web.stash.verifyBuildCommand.description', 'The command that will be run during a verification.') /} {param infoMessage: 'Examples:
  • ./scripts/verify.sh
  • ENVVAR=1 ENVVAR=2 ./scripts/verifytest.sh
  • ./stashbot/check.sh
' /} {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} {param value: $verifyBuildCommand /} @@ -347,7 +350,7 @@ {param content} {call aui.form.textField} {param id: 'prebuildCommand' /} - {param descriptionText: stash_i18n('stash.web.stash.prebuildCommand.description', 'Command to run before all builds (and for PR verifies, before the merge)') /} + {param descriptionText: stash_i18n('stash.web.stash.prebuildCommand.description', 'Command to run before all builds (and for PR verifies, before the merge). This happens after any Global Preamble the admin may have set.') /} {param labelContent: stash_i18n('stash.web.stash.prebuildCommand.label', 'Pre Build Command') /} {param value: $prebuildCommand /} {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} @@ -437,6 +440,37 @@ {/param} {/call} + +

{call aui.expander.trigger} {param id: 'advanced-expand-documentation'/} From 745cc375cf40733892dc0deb7eaf8876b85f7dd5 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Tue, 1 Dec 2015 22:27:52 -0800 Subject: [PATCH 41/54] Fix formatting on this chunk of code. --- .../config/ConfigurationPersistenceImpl.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java index 4b137dc..03008e3 100644 --- a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java +++ b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java @@ -458,19 +458,13 @@ public void setRepositoryConfigurationForRepository(Repository repo, new DBParam("ARTIFACTS_PATH", artifactsPath), new DBParam("REBUILD_ON_TARGET_UPDATE", rebuildOnUpdate), new DBParam("BUILD_TIMEOUT", buildTimeout), - new DBParam("EMAIL_NOTIFICATIONS_ENABLED", emailSettings - .getEmailNotificationsEnabled()), - new DBParam("EMAIL_FOR_EVERY_UNSTABLE_BUILD", emailSettings - .getEmailForEveryUnstableBuild()), - new DBParam("EMAIL_PER_MODULE_EMAIL", emailSettings - .getEmailPerModuleEmail()), - new DBParam("EMAIL_RECIPIENTS", emailSettings - .getEmailRecipients()), - new DBParam("EMAIL_SEND_TO_INDIVIDUALS", emailSettings - .getEmailSendToIndividuals()), new DBParam( - "STRICT_VERIFY_MODE", strictVerifyMode), - new DBParam("PRESERVE_JENKINS_JOB_CONFIG", - preserveJenkinsJobConfig)); + new DBParam("EMAIL_NOTIFICATIONS_ENABLED", emailSettings.getEmailNotificationsEnabled()), + new DBParam("EMAIL_FOR_EVERY_UNSTABLE_BUILD", emailSettings.getEmailForEveryUnstableBuild()), + new DBParam("EMAIL_PER_MODULE_EMAIL", emailSettings.getEmailPerModuleEmail()), + new DBParam("EMAIL_RECIPIENTS", emailSettings.getEmailRecipients()), + new DBParam("EMAIL_SEND_TO_INDIVIDUALS", emailSettings.getEmailSendToIndividuals()), + new DBParam("STRICT_VERIFY_MODE", strictVerifyMode), + new DBParam("PRESERVE_JENKINS_JOB_CONFIG",preserveJenkinsJobConfig)); if (maxVerifyChain != null) { rc.setMaxVerifyChain(maxVerifyChain); } From f94a1b85b3ee936d4d8c51c53114e062060ed970 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Tue, 1 Dec 2015 19:20:58 -0800 Subject: [PATCH 42/54] Add Jenkins build result expiry options -Expose number of days and number of builds -These are advanced options for verification and publish -Group verification and verify_pr since most of the time PRs aren't that big -The first time we're trying out using default values and maximum values stored in one place --- .../config/ConfigurationPersistenceImpl.java | 42 ++++- .../ConfigurationPersistenceService.java | 51 +++++- .../jobtemplate/JenkinsJobXmlFormatter.java | 7 +- .../persistence/RepositoryConfiguration.java | 25 +++ .../servlet/RepoConfigurationServlet.java | 7 + src/main/resources/jenkins-publish-job.vm | 6 + src/main/resources/jenkins-verify-job.vm | 6 + .../jenkins-verify-pull-request-job.vm | 6 + .../static/repository-configuration-panel.soy | 163 ++++++++++++------ .../stashbot/config/ConfigurationTest.java | 5 +- 10 files changed, 262 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java index 03008e3..d14d169 100644 --- a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java +++ b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java @@ -278,7 +278,8 @@ public void setRepositoryConfigurationForRepository(Repository repo, verifyBranchRegex, verifyBuildCommand, false, "N/A", publishBranchRegex, publishBuildCommand, false, "N/A", prebuildCommand, null, rebuildOnUpdate, false, "N/A", - rebuildOnUpdate, null, null, new EmailSettings(), false, false, -1); + rebuildOnUpdate, null, null, new EmailSettings(), false, false, -1, + new BuildResultExpirySettings()); } /* @@ -324,6 +325,8 @@ public void setRepositoryConfigurationForRepositoryFromRequest( EmailSettings emailSettings = getEmailSettings(req); + BuildResultExpirySettings expirySettings = getBuildExpirySettings(req); + String buildTimeoutStr = req.getParameter("buildTimeout"); Integer buildTimeout = null; if (buildTimeoutStr != null && !buildTimeoutStr.trim().isEmpty()) { @@ -337,7 +340,7 @@ public void setRepositoryConfigurationForRepositoryFromRequest( isPublishPinned, publishLabel, prebuildCommand, jenkinsServerName, rebuildOnUpdate, junitEnabled, junitPath, artifactsEnabled, artifactsPath, maxVerifyChain, emailSettings, - strictVerifyMode, preserveJenkinsJobConfig, buildTimeout); + strictVerifyMode, preserveJenkinsJobConfig, buildTimeout, expirySettings); RepositoryConfiguration rc = getRepositoryConfigurationForRepository(repo); setJobTypeStatusMapping(rc, JobType.VERIFY_COMMIT, getBoolean(req, "verificationEnabled")); @@ -384,6 +387,23 @@ private GlobalBuildCommandSettings getGlobalBuildCommands(HttpServletRequest req return new GlobalBuildCommandSettings(prebuild); } + private BuildResultExpirySettings getBuildExpirySettings(HttpServletRequest req) { + Integer verifyDays = Integer.parseInt(req.getParameter("verifyBuildExpiryDays")); + Integer verifyNumber = Integer.parseInt(req.getParameter("verifyBuildExpiryNumber")); + Integer publishDays = Integer.parseInt(req.getParameter("publishBuildExpiryDays")); + Integer publishNumber = Integer.parseInt(req.getParameter("publishBuildExpiryNumber")); + + Integer maxDays = Integer.parseInt(BuildResultExpirySettings.MAX_DAYS); + Integer maxNumber = Integer.parseInt(BuildResultExpirySettings.MAX_NUMBER); + + validateIntegerRange(verifyDays, 1, maxDays, "Days to keep verify build results", "days"); + validateIntegerRange(verifyNumber, 1, maxNumber, "Number of verify build results to keep", "builds"); + validateIntegerRange(publishDays, 1, maxDays, "Days to keep publish build results", "days"); + validateIntegerRange(publishNumber, 1, maxNumber, "Number of publish build results to keep", "builds"); + + return new BuildResultExpirySettings(verifyDays, verifyNumber, publishDays, publishNumber); + } + private EmailSettings getEmailSettings(HttpServletRequest req) { Boolean emailNotificationsEnabled = getBoolean(req, "isEmailNotificationsEnabled"); @@ -426,7 +446,8 @@ public void setRepositoryConfigurationForRepository(Repository repo, boolean isJunitEnabled, String junitPath, boolean artifactsEnabled, String artifactsPath, Integer maxVerifyChain, EmailSettings emailSettings, boolean strictVerifyMode, - Boolean preserveJenkinsJobConfig, Integer buildTimeout) throws SQLException, + Boolean preserveJenkinsJobConfig, Integer buildTimeout, + BuildResultExpirySettings expirySettings) throws SQLException, IllegalArgumentException { if (jenkinsServerName == null) { jenkinsServerName = DEFAULT_JENKINS_SERVER_CONFIG_KEY; @@ -458,6 +479,10 @@ public void setRepositoryConfigurationForRepository(Repository repo, new DBParam("ARTIFACTS_PATH", artifactsPath), new DBParam("REBUILD_ON_TARGET_UPDATE", rebuildOnUpdate), new DBParam("BUILD_TIMEOUT", buildTimeout), + new DBParam("VERIFY_BUILD_EXPIRY_DAYS", expirySettings.getVerifyDays()), + new DBParam("VERIFY_BUILD_EXPIRY_NUMBER", expirySettings.getVerifyNumber()), + new DBParam("PUBLISH_BUILD_EXPIRY_DAYS", expirySettings.getPublishDays()), + new DBParam("PUBLISH_BUILD_EXPIRY_NUMBER", expirySettings.getPublishNumber()), new DBParam("EMAIL_NOTIFICATIONS_ENABLED", emailSettings.getEmailNotificationsEnabled()), new DBParam("EMAIL_FOR_EVERY_UNSTABLE_BUILD", emailSettings.getEmailForEveryUnstableBuild()), new DBParam("EMAIL_PER_MODULE_EMAIL", emailSettings.getEmailPerModuleEmail()), @@ -507,6 +532,10 @@ public void setRepositoryConfigurationForRepository(Repository repo, foundRepo.setStrictVerifyMode(strictVerifyMode); foundRepo.setPreserveJenkinsJobConfig(preserveJenkinsJobConfig); foundRepo.setBuildTimeout(buildTimeout); + foundRepo.setVerifyBuildExpiryDays(expirySettings.getVerifyDays()); + foundRepo.setVerifyBuildExpiryNumber(expirySettings.getVerifyNumber()); + foundRepo.setPublishBuildExpiryDays(expirySettings.getPublishDays()); + foundRepo.setPublishBuildExpiryNumber(expirySettings.getPublishNumber()); foundRepo.save(); } @@ -571,6 +600,13 @@ public void validateBuildTimeout (Integer buildTimeout) throws IllegalArgumentEx JenkinsServerConfiguration.BUILD_TIMEOUT_MINUTES_DEFAULT + " minutes)."); } } + + private void validateIntegerRange(int value, int min, int max, String name, String unit) { + if (value < min || value > max) { + throw new IllegalArgumentException(name + " must be between " + min + " and " + max + " " + unit + "."); + } + } + /* * (non-Javadoc) * diff --git a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java index 02bf9f4..93ba514 100644 --- a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java +++ b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java @@ -87,7 +87,8 @@ public abstract void setRepositoryConfigurationForRepository( boolean isJunitEnabled, String junitPath, boolean artifactsEnabled, String artifactsPath, Integer maxVerifyChain, EmailSettings emailSettings, boolean strictVerifyMode, - Boolean preserveJenkinsJobConfig, Integer buildTimeout) throws SQLException, + Boolean preserveJenkinsJobConfig, Integer buildTimeout, + BuildResultExpirySettings expirySettings) throws SQLException, IllegalArgumentException; public abstract ImmutableCollection getAllJenkinsServerConfigurations() @@ -138,6 +139,54 @@ public abstract Boolean getJobTypeStatusMapping(RepositoryConfiguration rc, public abstract void setJobTypeStatusMapping(RepositoryConfiguration rc, JobType jt, Boolean isEnabled); + /* + * A class to contain the various values related + * to build expiry rules in Jenkins + */ + public static class BuildResultExpirySettings { + public static final String MAX_DAYS = "365"; + public static final String MAX_NUMBER = "1000"; + + public static final String DEFAULT_VERIFY_DAYS = "30"; + public static final String DEFAULT_VERIFY_NUMBER = "100"; + public static final String DEFAULT_PUBLISH_DAYS = "30"; + public static final String DEFAULT_PUBLISH_NUMBER = "100"; + + private final Integer verifyDays; + private final Integer verifyNumber; + private final Integer publishDays; + private final Integer publishNumber; + + public BuildResultExpirySettings() { + this(Integer.parseInt(DEFAULT_VERIFY_DAYS), Integer.parseInt(DEFAULT_VERIFY_NUMBER), + Integer.parseInt(DEFAULT_PUBLISH_DAYS), Integer.parseInt(DEFAULT_PUBLISH_NUMBER)); + } + + public BuildResultExpirySettings(Integer verifyDays, Integer verifyNumber, + Integer publishDays, Integer publishNumber) { + this.verifyDays = verifyDays; + this.verifyNumber = verifyNumber; + this.publishDays = publishDays; + this.publishNumber = publishNumber; + } + + public Integer getVerifyDays() { + return verifyDays; + } + + public Integer getVerifyNumber() { + return verifyNumber; + } + + public Integer getPublishDays() { + return publishDays; + } + + public Integer getPublishNumber() { + return publishNumber; + } + } + /* * A class to contain all our build * commands at the Global or Jenkins level. diff --git a/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java b/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java index 137879d..ba9b3c0 100644 --- a/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java +++ b/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java @@ -233,16 +233,21 @@ public String generateJobXml(JobTemplate jobTemplate, Repository repo) } vc.put("buildTimeout", buildTimeout); - // insert pinned data + + // insert pinned data and expiry info switch (jobTemplate.getJobType()) { case VERIFY_COMMIT: case VERIFY_PR: vc.put("isPinned", rc.getVerifyPinned()); vc.put("label", rc.getVerifyLabel()); + vc.put("verifyBuildExpiryDays", rc.getVerifyBuildExpiryDays()); + vc.put("verifyBuildExpiryNumber", rc.getVerifyBuildExpiryNumber()); break; case PUBLISH: vc.put("isPinned", rc.getPublishPinned()); vc.put("label", rc.getPublishLabel()); + vc.put("publishBuildExpiryDays", rc.getPublishBuildExpiryDays()); + vc.put("publishBuildExpiryNumber", rc.getPublishBuildExpiryNumber()); break; case NOOP: vc.put("isPinned", false); diff --git a/src/main/java/com/palantir/stash/stashbot/persistence/RepositoryConfiguration.java b/src/main/java/com/palantir/stash/stashbot/persistence/RepositoryConfiguration.java index 229839e..b7290cf 100644 --- a/src/main/java/com/palantir/stash/stashbot/persistence/RepositoryConfiguration.java +++ b/src/main/java/com/palantir/stash/stashbot/persistence/RepositoryConfiguration.java @@ -20,6 +20,8 @@ import net.java.ao.schema.Table; import net.java.ao.schema.Unique; +import com.palantir.stash.stashbot.config.ConfigurationPersistenceService.BuildResultExpirySettings; + @Table("RepoConfig001") @Preload public interface RepositoryConfiguration extends Entity { @@ -187,4 +189,27 @@ public interface RepositoryConfiguration extends Entity { public Integer getBuildTimeout(); public void setBuildTimeout(Integer buildTimeout); + + + @NotNull + @Default(BuildResultExpirySettings.DEFAULT_VERIFY_DAYS) + public Integer getVerifyBuildExpiryDays(); + public void setVerifyBuildExpiryDays(Integer verifyBuildExpiryDays); + + + @NotNull + @Default(BuildResultExpirySettings.DEFAULT_VERIFY_NUMBER) + public Integer getVerifyBuildExpiryNumber(); + public void setVerifyBuildExpiryNumber(Integer verifyBuildExpiryNumber); + + @NotNull + @Default(BuildResultExpirySettings.DEFAULT_PUBLISH_DAYS) + public Integer getPublishBuildExpiryDays(); + public void setPublishBuildExpiryDays(Integer publishBuildExpiryDays); + + + @NotNull + @Default(BuildResultExpirySettings.DEFAULT_PUBLISH_NUMBER) + public Integer getPublishBuildExpiryNumber(); + public void setPublishBuildExpiryNumber(Integer publishBuildExpiryNumber); } diff --git a/src/main/java/com/palantir/stash/stashbot/servlet/RepoConfigurationServlet.java b/src/main/java/com/palantir/stash/stashbot/servlet/RepoConfigurationServlet.java index 6bf2d96..cc75054 100644 --- a/src/main/java/com/palantir/stash/stashbot/servlet/RepoConfigurationServlet.java +++ b/src/main/java/com/palantir/stash/stashbot/servlet/RepoConfigurationServlet.java @@ -43,6 +43,7 @@ import com.atlassian.webresource.api.assembler.PageBuilderService; import com.google.common.collect.ImmutableMap; import com.palantir.stash.stashbot.config.ConfigurationPersistenceService; +import com.palantir.stash.stashbot.config.ConfigurationPersistenceService.BuildResultExpirySettings; import com.palantir.stash.stashbot.jobtemplate.JobType; import com.palantir.stash.stashbot.logger.PluginLoggerFactory; import com.palantir.stash.stashbot.managers.JenkinsManager; @@ -169,6 +170,12 @@ public void doGet(HttpServletRequest req, HttpServletResponse res) throws Servle .put("buildTimeout", rc.getBuildTimeout()) .put("buildTimeoutMin", JenkinsServerConfiguration.BUILD_TIMEOUT_MINUTES_MIN) .put("buildTimeoutMax", JenkinsServerConfiguration.BUILD_TIMEOUT_MINUTES_MAX) + .put("verifyBuildExpiryDays", rc.getVerifyBuildExpiryDays()) + .put("verifyBuildExpiryNumber", rc.getVerifyBuildExpiryNumber()) + .put("publishBuildExpiryDays", rc.getPublishBuildExpiryDays()) + .put("publishBuildExpiryNumber", rc.getPublishBuildExpiryNumber()) + .put("buildExpiryMaxDays", BuildResultExpirySettings.MAX_DAYS) + .put("buildExpiryMaxNumber", BuildResultExpirySettings.MAX_NUMBER) .put("isLocked", isLocked(theJsc)) .put("verificationEnabled", configurationPersistanceManager.getJobTypeStatusMapping(rc, JobType.VERIFY_COMMIT)) diff --git a/src/main/resources/jenkins-publish-job.vm b/src/main/resources/jenkins-publish-job.vm index a0555a2..5b9d09b 100644 --- a/src/main/resources/jenkins-publish-job.vm +++ b/src/main/resources/jenkins-publish-job.vm @@ -25,6 +25,12 @@ This is the default publish job created automatically by stashbot, and cannot be changed in Jenkins. Stash Project Link: <a href="$esc.xml($cleanRepositoryUrl)">$esc.xml($cleanRepositoryUrl)</a> + + $publishBuildExpiryDays + $publishBuildExpiryNumber + -1 + -1 + false diff --git a/src/main/resources/jenkins-verify-job.vm b/src/main/resources/jenkins-verify-job.vm index afe5c45..4f70653 100644 --- a/src/main/resources/jenkins-verify-job.vm +++ b/src/main/resources/jenkins-verify-job.vm @@ -23,6 +23,12 @@ This is the default verify job created automatically by stashbot, and cannot be changed in Jenkins. Stash Project Link: <a href="$esc.xml($cleanRepositoryUrl)">$esc.xml($cleanRepositoryUrl)</a> + + $verifyBuildExpiryDays + $verifyBuildExpiryNumber + -1 + -1 + false diff --git a/src/main/resources/jenkins-verify-pull-request-job.vm b/src/main/resources/jenkins-verify-pull-request-job.vm index 76363d3..1b2264c 100644 --- a/src/main/resources/jenkins-verify-pull-request-job.vm +++ b/src/main/resources/jenkins-verify-pull-request-job.vm @@ -31,6 +31,12 @@ This is the default verify job created automatically by stashbot, and cannot be changed in Jenkins. Stash Project Link: <a href="$esc.xml($cleanRepositoryUrl)">$esc.xml($cleanRepositoryUrl)</a> + + $verifyBuildExpiryDays + $verifyBuildExpiryNumber + -1 + -1 + false diff --git a/src/main/resources/static/repository-configuration-panel.soy b/src/main/resources/static/repository-configuration-panel.soy index 85aa77a..beca076 100644 --- a/src/main/resources/static/repository-configuration-panel.soy +++ b/src/main/resources/static/repository-configuration-panel.soy @@ -35,6 +35,12 @@ * @param buildTimeout * @param buildTimeoutMin * @param buildTimeoutMax + * @param verifyBuildExpiryDays + * @param verifyBuildExpiryNumber + * @param publishBuildExpiryDays + * @param publishBuildExpiryNumber + * @param buildExpiryMaxDays + * @param buildExpiryMaxNumber * **/ {template .repositoryConfigurationPanel} @@ -128,19 +134,49 @@ {call aui.expander.content} {param id: 'advanced-expand-publish-content'/} {param content} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.publish-pinned-checkbox.button.description', 'Restrict Jenkins Nodes') /} - {param fields: [[ - 'id': 'isPublishPinned', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), - 'isChecked': $isPublishPinned - ]] /} - {/call} - {call aui.form.textField} - {param id: 'publishLabel' /} - {param labelContent: stash_i18n('stash.web.stash.verifyLabel.label', 'Jenkins Node Label') /} - {param value: $publishLabel /} - {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Jenkins node label to restrict publish builds to') /} + {call aui.group.group} + {param content} + {call aui.group.item} + {param content} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.publish-pinned-checkbox.button.description', 'Restrict Jenkins Nodes') /} + {param fields: [[ + 'id': 'isPublishPinned', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $isPublishPinned + ]] /} + {/call} + {call aui.form.textField} + {param id: 'publishLabel' /} + {param labelContent: stash_i18n('stash.web.stash.verifyLabel.label', 'Jenkins Node Label') /} + {param value: $publishLabel /} + {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Jenkins node label to restrict publish builds to') /} + {/call} + {/param} + {/call} + {call aui.group.item} + {param content} +

Result Expiration

+
Build results meeting either of these conditions will be deleted. + {call aui.form.textField} + {param id: 'publishBuildExpiryDays' /} + {param labelContent: 'Days to keep' /} + {param descriptionText: 'The number of days to keep build results.' /} + {param value: $publishBuildExpiryDays /} + {param fieldWidth: 'short' /} + {param validationArguments: [ 'min' : 1, 'max' : $buildExpiryMaxDays ] /} + {/call} + {call aui.form.textField} + {param id: 'publishBuildExpiryNumber' /} + {param labelContent: 'Builds to keep' /} + {param descriptionText: 'The maximum number of build results to keep.' /} + {param value: $publishBuildExpiryNumber /} + {param fieldWidth: 'short' /} + {param validationArguments: [ 'min' : 1, 'max' : $buildExpiryMaxNumber ] /} + {/call} + {/param} + {/call} + {/param} {/call} {/param} {/call} @@ -199,42 +235,71 @@ {call aui.expander.content} {param id: 'advanced-expand-verify-content'/} {param content} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.verify-pinned-checkbox.button.description', 'Restrict Jenkins Nodes') /} - {param fields: [[ - 'id': 'isVerifyPinned', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), - 'isChecked': $isVerifyPinned - ]] /} - {/call} - {call aui.form.textField} - {param id: 'verifyLabel' /} - {param labelContent: stash_i18n('stash.web.stash.verifyLabel.label', 'Jenkins Node Label') /} - {param value: $verifyLabel /} - {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Jenkins node label to restrict publish builds to') /} - {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} - {/call} - {call aui.form.textField} - {param id: 'maxVerifyChain' /} - {param labelContent: stash_i18n('stash.web.stash.maxVerifyChain.label', 'Maximum number of commits to verify on a single push') /} - {param value: $maxVerifyChain /} - {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} - {/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.rebuild-on-update-radio.button.description', 'Rebuild PR on target change') /} - {param fields: [[ - 'id': 'rebuildOnUpdate', - 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), - 'isChecked': $rebuildOnUpdate - ]] /} - {/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.strict-verify-mode.button.description', 'Strict Verify Mode') /} - {param fields: [[ - 'id': 'isStrictVerifyMode', - 'labelText': stash_i18n('stash.web.stash.strict-verify-mode.button.label', 'Enabled'), - 'isChecked': $isStrictVerifyMode - ]] /} + {call aui.group.group} + {param content} + {call aui.group.item} + {param content} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.verify-pinned-checkbox.button.description', 'Restrict Jenkins Nodes') /} + {param fields: [[ + 'id': 'isVerifyPinned', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $isVerifyPinned + ]] /} + {/call} + {call aui.form.textField} + {param id: 'verifyLabel' /} + {param labelContent: stash_i18n('stash.web.stash.verifyLabel.label', 'Jenkins Node Label') /} + {param value: $verifyLabel /} + {param descriptionText: stash_i18n('stash.web.stash.verifyBranchRegex.description', 'Jenkins node label to restrict publish builds to') /} + {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} + {/call} + {call aui.form.textField} + {param id: 'maxVerifyChain' /} + {param labelContent: stash_i18n('stash.web.stash.maxVerifyChain.label', 'Maximum number of commits to verify on a single push') /} + {param value: $maxVerifyChain /} + {param validationArguments: [ 'minlength' : 1, 'maxlength' : 255 ] /} + {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.rebuild-on-update-radio.button.description', 'Rebuild PR on target change') /} + {param fields: [[ + 'id': 'rebuildOnUpdate', + 'labelText': stash_i18n('stash.web.stash.enable-ci-radio.button.label', 'Enabled'), + 'isChecked': $rebuildOnUpdate + ]] /} + {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.strict-verify-mode.button.description', 'Strict Verify Mode') /} + {param fields: [[ + 'id': 'isStrictVerifyMode', + 'labelText': stash_i18n('stash.web.stash.strict-verify-mode.button.label', 'Enabled'), + 'isChecked': $isStrictVerifyMode + ]] /} + {/call} + {/param} + {/call} + {call aui.group.item} + {param content} +

Result Expiration

+ {call aui.form.textField} + {param id: 'verifyBuildExpiryDays' /} + {param labelContent: 'Days to keep' /} + {param descriptionText: 'The number of days to keep build results.' /} + {param value: $verifyBuildExpiryDays /} + {param fieldWidth: 'short' /} + {param validationArguments: [ 'min' : 1, 'max' : $buildExpiryMaxDays ] /} + {/call} + {call aui.form.textField} + {param id: 'verifyBuildExpiryNumber' /} + {param labelContent: 'Builds to keep' /} + {param descriptionText: 'The number of build results to keep.' /} + {param value: $verifyBuildExpiryNumber /} + {param fieldWidth: 'short' /} + {param validationArguments: [ 'min' : 1, 'max' : $buildExpiryMaxNumber ] /} + {/call} + {/param} + {/call} + {/param} {/call} {/param} {/call} /* Expanded Group End */ diff --git a/src/test/java/com/palantir/stash/stashbot/config/ConfigurationTest.java b/src/test/java/com/palantir/stash/stashbot/config/ConfigurationTest.java index 2f34889..97e9016 100644 --- a/src/test/java/com/palantir/stash/stashbot/config/ConfigurationTest.java +++ b/src/test/java/com/palantir/stash/stashbot/config/ConfigurationTest.java @@ -42,6 +42,7 @@ import com.atlassian.stash.repository.Repository; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.palantir.stash.stashbot.config.ConfigurationPersistenceService.BuildResultExpirySettings; import com.palantir.stash.stashbot.config.ConfigurationPersistenceService.EmailSettings; import com.palantir.stash.stashbot.config.ConfigurationTest.DataStuff; import com.palantir.stash.stashbot.event.StashbotMetadataUpdatedEvent; @@ -183,7 +184,7 @@ public void storesRepoData() throws Exception { "verifyBranchRegex", "verifyBuildCommand", false, "N/A", "publishBranchRegex", "publishBuildCommand", false, "N/A", "prebuildCommand", "default", true, false, "N/A", false, "N/A", - size, new EmailSettings(true, "a@a.a", true, true, true), false, false, -1); + size, new EmailSettings(true, "a@a.a", true, true, true), false, false, -1, new BuildResultExpirySettings()); RepositoryConfiguration rc = cpm .getRepositoryConfigurationForRepository(repo); @@ -212,7 +213,7 @@ public void failsWithBadData() throws Exception { "verifyBranchRegex", "verifyBuildCommand", false, "N/A", "publishBranchRegex", "publishBuildCommand", false, "N/A", "prebuildCommand", "BADNAME", true, false, - "N/A", false, "N/A", null, new EmailSettings(), false, false, -1); + "N/A", false, "N/A", null, new EmailSettings(), false, false, -1, new BuildResultExpirySettings()); Assert.fail("Should have thrown exception"); } catch (Exception e) { // success From ca6a35d15e6e83105bc1ad86ac4a34639437d9c3 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Mon, 7 Dec 2015 15:59:52 -0800 Subject: [PATCH 43/54] Stashbot Admin UI Settings Changes -Admin actions moved to bottom -Admin action links are now buttons -Each jenkins config is now a tab so it's easier to see them -New jenkins config is also a tab -Group Stashbot config options --- .../servlet/JenkinsConfigurationServlet.java | 12 +- .../static/jenkins-configuration-panel.soy | 516 ++++++++++-------- 2 files changed, 305 insertions(+), 223 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java b/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java index 2f56caa..6519657 100644 --- a/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java +++ b/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java @@ -45,7 +45,7 @@ public class JenkinsConfigurationServlet extends HttpServlet { private final String PATH_PREFIX = "/stashbot/jenkins-admin"; /** - * + * */ private static final long serialVersionUID = 1L; @@ -156,12 +156,22 @@ public void doGet(HttpServletRequest req, HttpServletResponse res) throws Servle pageBuilderService.assembler().resources().requireContext("plugin.page.stashbot"); ImmutableCollection jenkinsConfigs = configurationPersistanceManager.getAllJenkinsServerConfigurations(); + + // Pick default pane + String defaultJenkinsConfig = null; + for (JenkinsServerConfiguration i : jenkinsConfigs) { + if (defaultJenkinsConfig == null || i.getName().equals("default")) { + defaultJenkinsConfig = i.getName(); + } + } + soyTemplateRenderer.render(res.getWriter(), "com.palantir.stash.stashbot:stashbotConfigurationResources", "plugin.page.stashbot.jenkinsConfigurationPanel", ImmutableMap. builder() .put("relUrl", relUrl) .put("jenkinsConfigs", jenkinsConfigs) + .put("defaultJenkinsConfig", defaultJenkinsConfig) .put("error", error) .put("notice", notice) .put("authenticationModeData", authDataBuilder.build()) diff --git a/src/main/resources/static/jenkins-configuration-panel.soy b/src/main/resources/static/jenkins-configuration-panel.soy index ae2e498..d8ce3e5 100644 --- a/src/main/resources/static/jenkins-configuration-panel.soy +++ b/src/main/resources/static/jenkins-configuration-panel.soy @@ -6,6 +6,7 @@ * @param error * @param notice * @param authenticationModeData + * @param defaultJenkinsConfig * Xparam authenticationModeDataSelected **/ {template .jenkinsConfigurationPanel} @@ -36,241 +37,312 @@ {param content} {call aui.group.item} {param content} -

Stashbot Administrative Tasks

-
    -
  • Create any plans that don't already exist HERE
  • -
  • Update/Create all plans HERE
  • -
+

{{stash_i18n('stash.web.repository.stashbot.heading', 'Stashbot Jenkins Settings')}}

{/param} {/call} {/param} {/call} {call aui.group.group} {param content} - {call aui.group.item} - {param content} -

{{stash_i18n('stash.web.repository.stashbot.heading', 'Stashbot Jenkins Settings')}}

- {/param} - {/call} +
+ + {foreach $jenkinsConfig in $jenkinsConfigs} +
+ {call aui.form.form} + {param action: $relUrl /} + {param content} + {call aui.group.group} + {param content} + {call aui.form.textField} + {param id: 'name' /} + {param labelContent: stash_i18n('stash.web.stash.jenkins.name.label', 'Jenkins Server Name') /} + {param value: $jenkinsConfig.name /} + {param descriptionText: stash_i18n('stash.web.stash.jenkins.name.description', 'This name will be used by users setting up Stashbot to pick a Jenkins server.') /} + {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.locked.button.description', 'Force as Default Server') /} + {param descriptionText: 'Locking this Jenkins server forces repository owners to use this Jenkins server configuration by default. For a repo to use a different server, a System Admin needs to change it for them.' /} + {param fields: [[ + 'id': 'locked', + 'labelText': stash_i18n('stash.web.stash.locked.button.label', 'Locked'), + 'isChecked': $jenkinsConfig.locked + ]] /} + {/call} + {/param} + {/call} + + {call aui.group.group} + {param content} +

Jenkins Settings

+ {call aui.form.textField} + {param id: 'url' /} + {param labelContent: stash_i18n('stash.web.stash.jenkins.url.label', 'Server URL') /} + {param value: $jenkinsConfig.url /} + {param descriptionText: stash_i18n('stash.web.stash.jenkins.url.description', 'Jenkins URL (e.g. http://jenkins.example.com:1234/)') /} + {/call} + {call aui.form.textField} + {param id: 'username' /} + {param labelContent: stash_i18n('stash.web.stash.jenkins.username.label', 'Username') /} + {param value: $jenkinsConfig.username /} + {/call} + {call aui.form.passwordField} + {param id: 'password' /} + {param labelContent: stash_i18n('stash.web.stash.jenkins.password.label', 'Password') /} + {param value: $jenkinsConfig.password /} + {/call} + {call aui.form.textField} + {param id: 'prefixTemplate' /} + {param labelContent: stash_i18n('stash.web.stash.prefixTemplate.label', 'Job Folder') /} + {param value: $jenkinsConfig.prefixTemplate /} + {param descriptionText: stash_i18n('stash.web.stash.prefixTemplate.description', 'The folder Stashbot will place jobs in Jenkins. If you are not using the Jenkins Folders plugin, or do not want to use subfolders, just use /. The folder can be specified using the template variables $project and $repo') /} + {/call} + {call aui.form.textField} + {param id: 'jobTemplate' /} + {param labelContent: stash_i18n('stash.web.stash.jobTemplate.label', 'Job Name Template') /} + {param value: $jenkinsConfig.jobTemplate /} + {param descriptionText: stash_i18n('stash.web.stash.jobTemplate.description', 'The name of the jobs created. It supports template variables $project and $repo. The job type will be appended at the end.') /} + {/call} + {/param} + {/call} + + {call aui.group.group} + {param content} +

Stash Settings

+ {call aui.form.selectField} + {param id: 'authenticationMode' /} + {param labelContent: stash_i18n('stash.web.stash.authenticationMode.label', 'Authentication Mode') /} + {param options: $authenticationModeData[$jenkinsConfig.name] /} + {param value: $jenkinsConfig.authenticationModeStr /} + {param descriptionText: 'The authentication mode to use for jenkins to talk back to stash. If you select Manual Credentials, create it in jenkins first then add the UUID of the credential in the password field, while using a valid username for stash in the username field (which is still used for comments on pull requests, etc.)' /} + {/call} + {call aui.form.textField} + {param id: 'stashUsername' /} + {param labelContent: stash_i18n('stash.web.stash.jenkins.stashusername.label', 'Username') /} + {param value: $jenkinsConfig.stashUsername /} + {param descriptionText: stash_i18n('stash.web.stash.jenkins.stashusername.description', 'Stash user that can clone the appropriate repos. This user will also comment on PRs. If the user does not exist, Stashbot will create it with the password below.') /} + {/call} + {call aui.form.passwordField} + {param id: 'stashPassword' /} + {param labelContent: stash_i18n('stash.web.stash.jenkins.stashpassword.label', 'Password or Jenkins Cred ID') /} + {param value: $jenkinsConfig.stashPassword /} + {param descriptionText: stash_i18n('stash.web.stash.jenkins.stashpassword.description', 'Password for the username. In Credential UUID mode, it is a Jenkins Credential ID. Supports $password and $repo replacement in Credential UUID mode.') /} + {/call} + {/param} + {/call} + + {call aui.group.group} + {param content} +

Job Settings

+ {call aui.form.textField} + {param id: 'maxVerifyChain' /} + {param labelContent: stash_i18n('stash.web.stash.maxVerifyChain.label', 'Maximum Commit Verifications') /} + {param value: ($jenkinsConfig.maxVerifyChain == 0 ? '0' : $jenkinsConfig.maxVerifyChain) /} + {param descriptionText: stash_i18n('stash.web.stash.maxVerifyChain.description', 'The maximum number of builds to trigger for a single push. Individual repositories also have this setting, but they are limited by the setting of the jenkins server as well. Set to 0 for "no limit". You probably want to set this to between 50% and 200% of the number of executors your jenkins instance has, depending on how long your build takes, your expected latency, and load.') /} + {/call} + {call aui.form.textField} + {param id: 'defaultTimeout' /} + {param labelContent: stash_i18n('stash.web.stash.defaultTimeout.label', 'Default Build Timeout') /} + {param value: $jenkinsConfig.defaultTimeout /} + {param descriptionText: stash_i18n('stash.web.stash.defaultTimeout.description', 'The default timeout value (in minutes from 5 to 10080) for a Jenkins job created by Stashbot. This can be overriden on the repo level.') /} + {/call} + {call aui.form.textareaField} + {param id: 'globalPrebuild' /} + {param labelContent: stash_i18n('stash.web.stash.globalPrebuild.label', 'Global Pre-build Commands') /} + {param value: $jenkinsConfig.globalPrebuildCommand /} + {param descriptionText: stash_i18n('stash.web.stash.globalPrebuild.description', 'Command or commands to run before all builds. Useful for cleanup and global prep.') /} + {/call} + {/param} + {/call} + + {call aui.group.group} + {param content} + {call aui.form.buttons} + {param content} + {call aui.form.submit} + {param id: 'submit' /} + {param text: 'Save' /} + {param type: 'submit' /} + {/call} + {call stash.buttons.button} + {param id: 'delete' /} + {param buttonText: 'Delete Entry ' + $jenkinsConfig.name /} + {param href: $relUrl + '/delete/' + $jenkinsConfig.name /} + {/call} + {/param} + {/call} + {/param} + {/call} + + + {/param} + {/call} +
+ {/foreach} + +
+ {call aui.form.form} + {param action: $relUrl /} + {param content} + + {call aui.group.group} + {param content} + {call aui.form.textField} + {param id: 'name' /} + {param labelContent: stash_i18n('stash.web.stash.jenkins.name.label', 'Jenkins Server Name') /} + {param placeholderText: 'enter name here [a-zA-Z0-9]+' /} + {param descriptionText: stash_i18n('stash.web.stash.jenkins.name.description', 'This name will be used by users setting up Stashbot to pick a Jenkins server.') /} + {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.locked.button.description', 'Force as Default Server') /} + {param descriptionText: 'Locking this Jenkins server forces repository owners to use this Jenkins server configuration by default. For a repo to use a different server, a System Admin needs to change it for them.' /} + {param fields: [[ + 'id': 'locked', + 'labelText': stash_i18n('stash.web.stash.locked.button.label', 'Locked'), + ]] /} + {/call} + {/param} + {/call} + + {call aui.group.group} + {param content} +

Jenkins Settings

+ {call aui.form.textField} + {param id: 'url' /} + {param labelContent: stash_i18n('stash.web.stash.jenkins.url.label', 'Server URL') /} + {param placeholderText: 'http://jenkins.example.com:8080/' /} + {param descriptionText: stash_i18n('stash.web.stash.jenkins.url.description', 'Jenkins URL (e.g. http://jenkins.example.com:1234/)') /} + {/call} + {call aui.form.textField} + {param id: 'username' /} + {param labelContent: stash_i18n('stash.web.stash.jenkins.username.label', 'Username') /} + {param value: 'jenkins_user' /} + {/call} + {call aui.form.passwordField} + {param id: 'password' /} + {param labelContent: stash_i18n('stash.web.stash.jenkins.password.label', 'Password') /} + {param value: 'jenkins_password' /} + {/call} + {call aui.form.textField} + {param id: 'prefixTemplate' /} + {param labelContent: stash_i18n('stash.web.stash.prefixTemplate.label', 'Job Folder') /} + {param value: '/' /} + {param descriptionText: stash_i18n('stash.web.stash.prefixTemplate.description', 'The folder Stashbot will place jobs in Jenkins. If you are not using the Jenkins Folders plugin, or do not want to use subfolders, just use /. The folder can be specified using the template variables $project and $repo') /} + {/call} + {call aui.form.textField} + {param id: 'jobTemplate' /} + {param labelContent: stash_i18n('stash.web.stash.jobTemplate.label', 'Job Name Template') /} + {param value: '$project_$repo' /} + {param descriptionText: stash_i18n('stash.web.stash.jobTemplate.description', 'The name of the jobs created. It supports template variables $project and $repo. The job type will be appended at the end.') /} + {/call} + {/param} + {/call} + + {call aui.group.group} + {param content} +

Stash Settings

+ {call aui.form.selectField} + {param id: 'authenticationMode' /} + {param labelContent: stash_i18n('stash.web.stash.authenticationMode.label', 'Authentication Mode') /} + {param options: $authenticationModeData['default'] /} + {param descriptionText: 'The authentication mode to use for jenkins to talk back to stash. If you select Manual Credentials, create it in jenkins first then add the UUID of the credential in the password field, while using a valid username for stash in the username field (which is still used for comments on pull requests, etc.)' /} + {/call} + {call aui.form.textField} + {param id: 'stashUsername' /} + {param labelContent: stash_i18n('stash.web.stash.jenkins.stashusername.label', 'Username') /} + {param value: 'stash_user' /} + {param descriptionText: stash_i18n('stash.web.stash.jenkins.stashusername.description', 'Stash user that can clone the appropriate repos. This user will also comment on PRs. If the user does not exist, Stashbot will create it with the password below.') /} + {/call} + {call aui.form.passwordField} + {param id: 'stashPassword' /} + {param labelContent: stash_i18n('stash.web.stash.jenkins.stashpassword.label', 'Password or Jenkins Cred ID') /} + {param value: 'stash_password' /} + {param descriptionText: stash_i18n('stash.web.stash.jenkins.stashpassword.description', 'Password for the username. In Credential UUID mode, it is a Jenkins Credential ID. Supports $password and $repo replacement in Credential UUID mode.') /} + {/call} + {/param} + {/call} + + + {call aui.group.group} + {param content} +

Job Settings

+ {call aui.form.textField} + {param id: 'maxVerifyChain' /} + {param labelContent: stash_i18n('stash.web.stash.maxVerifyChain.label', 'Maximum Commit Verifications') /} + {param value: '10' /} + {param descriptionText: stash_i18n('stash.web.stash.maxVerifyChain.description', 'The maximum number of builds to trigger for a single push. Individual repositories also have this setting, but they are limited by the setting of the jenkins server as well. Set to 0 for "no limit". You probably want to set this to between 50% and 200% of the number of executors your jenkins instance has, depending on how long your build takes, your expected latency, and load.') /} + {/call} + {call aui.form.textField} + {param id: 'defaultTimeout' /} + {param labelContent: stash_i18n('stash.web.stash.defaultTimeout.label', 'Default Build Timeout') /} + {param value: '240' /} + {param descriptionText: stash_i18n('stash.web.stash.defaultTimeout.description', 'The default timeout value (in minutes from 5 to 10080) for a Jenkins job created by Stashbot. This can be overriden on the repo level.') /} + {/call} + {call aui.form.textareaField} + {param id: 'globalPrebuild' /} + {param labelContent: stash_i18n('stash.web.stash.globalPrebuild.label', 'Global Pre-build Commands') /} + {param value: '/bin/true' /} + {param descriptionText: stash_i18n('stash.web.stash.globalPrebuild.description', 'Command or commands to run before all builds. Useful for cleanup and global prep.') /} + {/call} + {/param} + {/call} + + + {call aui.form.buttons} + {param content} + {call aui.form.submit} + {param id: 'submit' /} + {param text: 'Save' /} + {param type: 'submit' /} + {/call} + {/param} + {/call} + + {/param} + {/call} +
+ +
+ {/param} {/call} + {call aui.group.group} {param content} - {foreach $jenkinsConfig in $jenkinsConfigs} -

{$jenkinsConfig.name}

- {call aui.form.form} - {param action: $relUrl /} - {param content} - {call aui.form.textField} - {param id: 'name' /} - {param labelContent: stash_i18n('stash.web.stash.jenkins.name.label', 'Jenkins Server Name') /} - {param value: $jenkinsConfig.name /} - {param descriptionText: stash_i18n('stash.web.stash.jenkins.name.description', 'Jenkins Server Name (referred to in the repository configuration)') /} - {/call} - {call aui.form.textField} - {param id: 'url' /} - {param labelContent: stash_i18n('stash.web.stash.jenkins.url.label', 'Jenkins URL') /} - {param value: $jenkinsConfig.url /} - {param descriptionText: stash_i18n('stash.web.stash.jenkins.url.description', 'Jenkins URL (e.g. http://jenkins.example.com:1234/)') /} - {/call} - {call aui.form.textField} - {param id: 'username' /} - {param labelContent: stash_i18n('stash.web.stash.jenkins.username.label', 'Jenkins Username') /} - {param value: $jenkinsConfig.username /} - {param descriptionText: stash_i18n('stash.web.stash.jenkins.description', 'Jenkins Username') /} - {/call} - {call aui.form.passwordField} - {param id: 'password' /} - {param labelContent: stash_i18n('stash.web.stash.jenkins.password.label', 'Jenkins Password') /} - {param value: $jenkinsConfig.password /} - {param descriptionText: stash_i18n('stash.web.stash.jenkins.description', 'Jenkins Password') /} - {/call} - {call aui.form.selectField} - {param id: 'authenticationMode' /} - {param labelContent: stash_i18n('stash.web.stash.authenticationMode.label', 'Stash Authentication Mode') /} - {param options: $authenticationModeData[$jenkinsConfig.name] /} - {param value: $jenkinsConfig.authenticationModeStr /} - {param descriptionText: stash_i18n('stash.web.stash.authenticationMode.description', 'Stash Authentication Mode') /} - {/call} - {call aui.form.textField} - {param id: 'stashUsername' /} - {param labelContent: stash_i18n('stash.web.stash.jenkins.stashusername.label', 'Stash Username') /} - {param value: $jenkinsConfig.stashUsername /} - {param descriptionText: stash_i18n('stash.web.stash.jenkins.stashusername.description', 'Stash Username') /} - {/call} - {call aui.form.passwordField} - {param id: 'stashPassword' /} - {param labelContent: stash_i18n('stash.web.stash.jenkins.stashpassword.label', 'Stash Password') /} - {param value: $jenkinsConfig.stashPassword /} - {param descriptionText: stash_i18n('stash.web.stash.jenkins.stashpassword.description', 'Stash Password. Supports $password and $repo replacement in Credential UUID mode.') /} - {/call} - {call aui.form.textField} - {param id: 'maxVerifyChain' /} - {param labelContent: stash_i18n('stash.web.stash.maxVerifyChain.label', 'Maximum number of commits to verify on a single push') /} - {param value: ($jenkinsConfig.maxVerifyChain == 0 ? '0' : $jenkinsConfig.maxVerifyChain) /} - {param descriptionText: stash_i18n('stash.web.stash.maxVerifyChain.description', 'Maximum number of commits to verify on a single push') /} - {/call} - {call aui.form.textField} - {param id: 'defaultTimeout' /} - {param labelContent: stash_i18n('stash.web.stash.defaultTimeout.label', 'Default Build Timeout') /} - {param value: $jenkinsConfig.defaultTimeout /} - {param descriptionText: stash_i18n('stash.web.stash.defaultTimeout.description', 'The default timeout value (in minutes from 5 to 10080) for a Jenkins job created by Stashbot. This can be overriden on the repo level.') /} - {/call} - {call aui.form.textareaField} - {param id: 'globalPrebuild' /} - {param labelContent: stash_i18n('stash.web.stash.globalPrebuild.label', 'Global Pre-build Commands') /} - {param value: $jenkinsConfig.globalPrebuildCommand /} - {param descriptionText: stash_i18n('stash.web.stash.globalPrebuild.description', 'Command or commands to run before all builds. Useful for cleanup and global prep.') /} - {/call} - {call aui.form.textField} - {param id: 'prefixTemplate' /} - {param labelContent: stash_i18n('stash.web.stash.prefixTemplate.label', 'Template used to put jobs in separate folders in jenkins') /} - {param value: $jenkinsConfig.prefixTemplate /} - {param descriptionText: stash_i18n('stash.web.stash.prefixTemplate.description', 'Template used to put jobs in separate folders in jenkins. Valid variables are $project and $repo') /} - {/call} - {call aui.form.textField} - {param id: 'jobTemplate' /} - {param labelContent: stash_i18n('stash.web.stash.jobTemplate.label', 'Template used to generate job names') /} - {param value: $jenkinsConfig.jobTemplate /} - {param descriptionText: stash_i18n('stash.web.stash.jobTemplate.description', 'Valid variables are $project and $repo. Template will have the job type appended') /} - {/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.locked.button.description', 'Lock projects into this configuration') /} - {param fields: [[ - 'id': 'locked', - 'labelText': stash_i18n('stash.web.stash.locked.button.label', 'Locked'), - 'isChecked': $jenkinsConfig.locked - ]] /} - {/call} - {call aui.form.buttons} - {param content} - {call aui.form.submit} - {param id: 'submit' /} - {param text: 'Save' /} - {param type: 'submit' /} - {/call} - {/param} - {/call} - {call stash.buttons.button} - {param id: 'delete' /} - {param buttonText: 'Delete Entry ' + $jenkinsConfig.name /} - {param href: $relUrl + '/delete/' + $jenkinsConfig.name /} - {/call} - {/param} - {/call} - {/foreach} -

New Jenkins Server

- {call aui.form.form} - {param action: $relUrl /} - {param content} - {call aui.form.textField} - {param id: 'name' /} - {param labelContent: stash_i18n('stash.web.stash.jenkins.name.label', 'Jenkins Server Name') /} - {param value: 'enter name here [a-zA-Z0-9]+' /} - {param descriptionText: stash_i18n('stash.web.stash.jenkins.name.description', 'Jenkins Server Name (referred to in the repository configuration)') /} - {/call} - {call aui.form.textField} - {param id: 'url' /} - {param labelContent: stash_i18n('stash.web.stash.jenkins.url.label', 'Jenkins URL') /} - {param value: 'http://jenkins.example.com:8080/' /} - {param descriptionText: stash_i18n('stash.web.stash.jenkins.url.description', 'Jenkins URL (e.g. http://jenkins.example.com:1234/)') /} - {/call} - {call aui.form.selectField} - {param id: 'authenticationMode' /} - {param labelContent: stash_i18n('stash.web.stash.authenticationMode.label', 'Stash Authentication Mode') /} - {param options: $authenticationModeData['default'] /} - {param descriptionText: stash_i18n('stash.web.stash.authenticationMode.description', 'Stash Authentication Mode') /} - {/call} - {call aui.form.textField} - {param id: 'username' /} - {param labelContent: stash_i18n('stash.web.stash.jenkins.username.label', 'Jenkins Username') /} - {param value: 'jenkins_user' /} - {param descriptionText: stash_i18n('stash.web.stash.jenkins.description', 'Jenkins Username') /} - {/call} - {call aui.form.passwordField} - {param id: 'password' /} - {param labelContent: stash_i18n('stash.web.stash.jenkins.password.label', 'Jenkins Password') /} - {param value: 'jenkins_password' /} - {param descriptionText: stash_i18n('stash.web.stash.jenkins.description', 'Jenkins Password') /} - {/call} - {call aui.form.textField} - {param id: 'stashUsername' /} - {param labelContent: stash_i18n('stash.web.stash.jenkins.stashusername.label', 'Stash Username') /} - {param value: 'stash_user' /} - {param descriptionText: stash_i18n('stash.web.stash.jenkins.stashusername.description', 'Stash Username') /} - {/call} - {call aui.form.passwordField} - {param id: 'stashPassword' /} - {param labelContent: stash_i18n('stash.web.stash.jenkins.stashpassword.label', 'Stash Password') /} - {param value: 'stash_password' /} - {param descriptionText: stash_i18n('stash.web.stash.jenkins.stashpassword.description', 'Stash Password') /} - {/call} - {call aui.form.textField} - {param id: 'maxVerifyChain' /} - {param labelContent: stash_i18n('stash.web.stash.maxVerifyChain.label', 'Maximum number of commits to verify on a single push') /} - {param value: '10' /} - {param descriptionText: stash_i18n('stash.web.stash.maxVerifyChain.description', 'Maximum number of commits to verify on a single push') /} - {/call} - {call aui.form.textField} - {param id: 'defaultTimeout' /} - {param labelContent: stash_i18n('stash.web.stash.defaultTimeout.label', 'Default Build Timeout') /} - {param value: '240' /} - {param descriptionText: stash_i18n('stash.web.stash.defaultTimeout.description', 'The default timeout value (in minutes from 5 to 10080) for a Jenkins job created by Stashbot. This can be overriden on the repo level.') /} - {/call} - {call aui.form.textareaField} - {param id: 'globalPrebuild' /} - {param labelContent: stash_i18n('stash.web.stash.globalPrebuild.label', 'Global Pre-build Commands') /} - {param value: '/bin/true' /} - {param descriptionText: stash_i18n('stash.web.stash.globalPrebuild.description', 'Command or commands to run before all builds. Useful for cleanup and global prep.') /} - {/call} - {call aui.form.textField} - {param id: 'prefixTemplate' /} - {param labelContent: stash_i18n('stash.web.stash.prefixTemplate.label', 'Template used to put jobs in separate folders in jenkins') /} - {param value: '/' /} - {param descriptionText: stash_i18n('stash.web.stash.prefixTemplate.description', 'Template used to put jobs in separate folders in jenkins. Valid variables are $project and $repo') /} - {/call} - {call aui.form.textField} - {param id: 'jobTemplate' /} - {param labelContent: stash_i18n('stash.web.stash.jobTemplate.label', 'Template used to generate job names') /} - {param value: '$project_$repo' /} - {param descriptionText: stash_i18n('stash.web.stash.jobTemplate.description', 'Valid variables are $project and $repo. Template will have the job type appended') /} - {/call} - {call aui.form.checkboxField} - {param legendContent: stash_i18n('stash.web.stash.locked.button.description', 'Lock projects into this configuration') /} - {param fields: [[ - 'id': 'locked', - 'labelText': stash_i18n('stash.web.stash.locked.button.label', 'Locked'), - ]] /} - {/call} - {call aui.form.buttons} - {param content} - {call aui.form.submit} - {param id: 'submit' /} - {param text: 'Save' /} - {param type: 'submit' /} - {/call} - {/param} - {/call} - {/param} +

Stashbot Administrative Tasks

+ {call aui.group.group} + {param content} + {call aui.group.item} + {param content} + {call stash.buttons.button} + {param id: 'button-create-missing-plans' /} + {param buttonText: 'Create Missing Plans' /} + {param href: $relUrl + '/create-new/.' /} + {/call} + {/param} + {/call} + {call aui.group.item} + {param content} + {call stash.buttons.button} + {param id: 'button-update-all-plans' /} + {param buttonText: 'Update/Create Plans' /} + {param href: $relUrl + '/reload-all/.' /} + {/call} + {/param} + {/call} + {/param} {/call} {/param} {/call} -
-

Configuring Jenkins:

-

-Jenkins configurations include the following fields: -

    -
  • name: The name users will see when they select this jenkins server
  • -
  • url: The URL for the jenkins instance. (e.g. https://jenkins.example.com:8080/)
  • -
  • Authentication Mode: The authentication mode to use for jenkins to talk back to stash. If you select Manual Credentials, create it in jenkins first then add the UUID of the credential in the password field, while using a valid username for stash in the username field (which is still used for comments on pull requests, etc.)
  • -
  • username: The username to use to log into jenkins (value is ignored if authentication is not enabled). -
  • password: The password to use to log into jenkins (value is ignored if authentication is not enabled). -
  • stashUsername: The username to use to log into stash from jenkins (for cloning repos and reporting build results). This account is automatically created if it does not exist already. It is recommended you generate a reasonable username/password pair. If you prefer for auditing purposes, you may use an account from crowd/LDAP by using the existing credentials in this field.
  • -
  • stashPassword: The password to use to log into stash from jenkins (same comment applies as for the username).
  • -
  • maxVerifyChain: The maximum number of builds to trigger for a single push. Individual repositories also have this setting, but they are limited by the setting of the jenkins server as well. Set to 0 for "no limit". You probably want to set this to between 50% and 200% of the number of executors your jenkins instance has, depending on how long your build takes, your expected latency, and load.
  • -
-

-
+ {/template} From 41fbc166bf5064077a245cab915e6bfc84a10833 Mon Sep 17 00:00:00 2001 From: Jared Shumway Date: Wed, 16 Dec 2015 20:38:32 -0800 Subject: [PATCH 44/54] Automatically recreate missing jobs in Jenkins --- .../stashbot/managers/JenkinsManager.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java index 224bce1..fcd7afd 100644 --- a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java +++ b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java @@ -265,7 +265,14 @@ public void synchronousTriggerBuild(Repository repo, JobType jobType, String key = jt.getBuildNameFor(repo, jsc); if (!jobMap.containsKey(key)) { - throw new RuntimeException("Build doesn't exist: " + key); + List templates = jtm.getJenkinsJobsForRepository(rc); + for (JobTemplate jobTemplate : templates) { + if (key.equals(jobTemplate.getBuildNameFor(repo, jsc))) { + log.info("Build " + key + "doesn't exist, creating it"); + createJob(repo, jobTemplate); + } + } + jobMap = js.getJobs(); } Builder builder = ImmutableMap.builder(); @@ -327,7 +334,14 @@ public void synchronousTriggerBuild(Repository repo, JobType jobType, String key = jt.getBuildNameFor(repo, jsc); if (!jobMap.containsKey(key)) { - throw new RuntimeException("Build doesn't exist: " + key); + List templates = jtm.getJenkinsJobsForRepository(rc); + for (JobTemplate jobTemplate : templates) { + if (key.equals(jobTemplate.getBuildNameFor(repo, jsc))) { + log.info("Build " + key + "doesn't exist, creating it"); + createJob(repo, jobTemplate); + } + } + jobMap = js.getJobs(); } Builder builder = ImmutableMap.builder(); From 1e6e9dab619eff8a2a2605ba4220acb9ad2fe847 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Mon, 4 Jan 2016 15:30:44 -0800 Subject: [PATCH 45/54] ITOOLS-16732 Change Submit button ID since it fixes the double click problem --- src/main/resources/static/repository-configuration-panel.soy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/static/repository-configuration-panel.soy b/src/main/resources/static/repository-configuration-panel.soy index beca076..82f0f91 100644 --- a/src/main/resources/static/repository-configuration-panel.soy +++ b/src/main/resources/static/repository-configuration-panel.soy @@ -495,7 +495,7 @@ {call aui.form.buttons} {param content} {call aui.form.submit} - {param id: 'submit' /} + {param id: 'submitstashbotsettings' /} {param text: stash_i18n('stash.web.stash.ci-prefs.submit', 'Save') /} {param type: 'submit' /} {/call} From c23478b9a18172b2cfd30cdc15582d5a96e922a9 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Fri, 22 Jan 2016 14:31:34 -0800 Subject: [PATCH 46/54] Add label for all Jenkins builds at Global Level -Adds column to the DB -Adds the text box in the actual Jenkins Server config --- .../config/ConfigurationPersistenceImpl.java | 20 ++++++++++++++----- .../ConfigurationPersistenceService.java | 2 +- .../JenkinsServerConfiguration.java | 4 ++++ .../static/jenkins-configuration-panel.soy | 12 +++++++++++ 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java index d14d169..5db142a 100644 --- a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java +++ b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceImpl.java @@ -136,9 +136,11 @@ public void setJenkinsServerConfigurationFromRequest(HttpServletRequest req) GlobalBuildCommandSettings globalBuildCommands = getGlobalBuildCommands(req); + String globalLabel = cleanGlobalLabel(req.getParameter("globalLabel")); + setJenkinsServerConfiguration(name, url, username, password, am, stashUsername, stashPassword, maxVerifyChain, defaultTimeout, - globalBuildCommands, prefixTemplate, jobTemplate, isLocked); + globalBuildCommands, prefixTemplate, jobTemplate, globalLabel, isLocked); } /* @@ -168,7 +170,7 @@ public void setJenkinsServerConfiguration(String name, String url, setJenkinsServerConfiguration(name, url, username, password, authenticationMode, stashUsername, stashPassword, maxVerifyChain, JenkinsServerConfiguration.BUILD_TIMEOUT_MINUTES_DEFAULT, - new GlobalBuildCommandSettings(), "/", "$project_$repo", false); + new GlobalBuildCommandSettings(), "/", "$project_$repo", null, false); } /* @@ -187,7 +189,7 @@ public void setJenkinsServerConfiguration(String name, String url, AuthenticationMode authenticationMode, String stashUsername, String stashPassword, Integer maxVerifyChain, Integer defaultTimeout, GlobalBuildCommandSettings globalBuildCommands, - String prefixTemplate, String jobTemplate, Boolean isLocked) throws SQLException { + String prefixTemplate, String jobTemplate, String globalLabel, Boolean isLocked) throws SQLException { if (name == null) { name = DEFAULT_JENKINS_SERVER_CONFIG_KEY; } @@ -208,8 +210,8 @@ public void setJenkinsServerConfiguration(String name, String url, "MAX_VERIFY_CHAIN", maxVerifyChain), new DBParam( "DEFAULT_TIMEOUT", defaultTimeout), new DBParam( "GLOBAL_PREBUILD_COMMAND", globalBuildCommands.getPrebuild()), new DBParam( - "PREFIX_TEMPLATE", prefixTemplate), new DBParam("JOB_TEMPLATE", jobTemplate), new DBParam("LOCKED", - isLocked)); + "PREFIX_TEMPLATE", prefixTemplate), new DBParam("JOB_TEMPLATE", jobTemplate), + new DBParam("GLOBAL_LABEL", cleanGlobalLabel(globalLabel)), new DBParam("LOCKED", isLocked)); return; } // already exists, so update it @@ -226,6 +228,7 @@ public void setJenkinsServerConfiguration(String name, String url, configs[0].setGlobalPrebuildCommand(globalBuildCommands.getPrebuild()); configs[0].setPrefixTemplate(prefixTemplate); configs[0].setJobTemplate(jobTemplate); + configs[0].setGlobalLabel(globalLabel); configs[0].setLocked(isLocked); configs[0].save(); } @@ -607,6 +610,13 @@ private void validateIntegerRange(int value, int min, int max, String name, Stri } } + private String cleanGlobalLabel(String label) { + if (label != null && label.trim().equals("")) { + return null; + } + return label; + } + /* * (non-Javadoc) * diff --git a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java index 93ba514..b8fc57a 100644 --- a/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java +++ b/src/main/java/com/palantir/stash/stashbot/config/ConfigurationPersistenceService.java @@ -61,7 +61,7 @@ public abstract void setJenkinsServerConfiguration(String name, String url, AuthenticationMode authenticationMode, String stashUsername, String stashPassword, Integer maxVerifyChain, Integer defaultTimeout, GlobalBuildCommandSettings globalBuildCommands, - String prefixTemplate, String jobTemplate, Boolean isLocked) throws SQLException; + String prefixTemplate, String jobTemplate, String globalLabel, Boolean isLocked) throws SQLException; public abstract RepositoryConfiguration getRepositoryConfigurationForRepository( Repository repo) throws SQLException; diff --git a/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java b/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java index d842427..85b6f54 100644 --- a/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java +++ b/src/main/java/com/palantir/stash/stashbot/persistence/JenkinsServerConfiguration.java @@ -218,6 +218,10 @@ public static ImmutableList> getSelectList( public void setGlobalPrebuildCommand(String command); + public String getGlobalLabel(); + public void setGlobalLabel(String label); + + // For security - allow a jenkins server config to be locked to // non-system-admins @NotNull diff --git a/src/main/resources/static/jenkins-configuration-panel.soy b/src/main/resources/static/jenkins-configuration-panel.soy index d8ce3e5..3e01258 100644 --- a/src/main/resources/static/jenkins-configuration-panel.soy +++ b/src/main/resources/static/jenkins-configuration-panel.soy @@ -154,6 +154,12 @@ {param value: $jenkinsConfig.defaultTimeout /} {param descriptionText: stash_i18n('stash.web.stash.defaultTimeout.description', 'The default timeout value (in minutes from 5 to 10080) for a Jenkins job created by Stashbot. This can be overriden on the repo level.') /} {/call} + {call aui.form.textField} + {param id: 'globalLabel' /} + {param labelContent: 'Global Job Label' /} + {param value: $jenkinsConfig.globalLabel /} + {param descriptionText: 'The default label all Stashbot jobs should have on this Jenkins server. This is useful for Jenkins servers with multiple pools of nodes. For no default label, leave blank.' /} + {/call} {call aui.form.textareaField} {param id: 'globalPrebuild' /} {param labelContent: stash_i18n('stash.web.stash.globalPrebuild.label', 'Global Pre-build Commands') /} @@ -286,6 +292,12 @@ {param value: '240' /} {param descriptionText: stash_i18n('stash.web.stash.defaultTimeout.description', 'The default timeout value (in minutes from 5 to 10080) for a Jenkins job created by Stashbot. This can be overriden on the repo level.') /} {/call} + {call aui.form.textField} + {param id: 'globalLabel' /} + {param labelContent: 'Global Job Label' /} + {param value: '' /} + {param descriptionText: 'The default label all Stashbot jobs should have on this Jenkins server. This is useful for Jenkins servers with multiple pools of nodes. For no default label, leave blank.' /} + {/call} {call aui.form.textareaField} {param id: 'globalPrebuild' /} {param labelContent: stash_i18n('stash.web.stash.globalPrebuild.label', 'Global Pre-build Commands') /} From 5b33121130d911446d67751fef62b3e0ded4da7d Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Fri, 22 Jan 2016 15:23:04 -0800 Subject: [PATCH 47/54] Add the global labels to the actual job configs -If no global, business as usual -If a global and no repo, just the global -If a global AND a repo, then global && ( repo ) -Needs escaping of labels since it's not straight alphanumerics anymore --- .../jobtemplate/JenkinsJobXmlFormatter.java | 24 +++++++++++++++---- src/main/resources/jenkins-publish-job.vm | 2 +- src/main/resources/jenkins-verify-job.vm | 2 +- .../jenkins-verify-pull-request-job.vm | 2 +- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java b/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java index ba9b3c0..842b2d6 100644 --- a/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java +++ b/src/main/java/com/palantir/stash/stashbot/jobtemplate/JenkinsJobXmlFormatter.java @@ -233,19 +233,20 @@ public String generateJobXml(JobTemplate jobTemplate, Repository repo) } vc.put("buildTimeout", buildTimeout); + boolean isGlobalLabel = jsc.getGlobalLabel() != null && !jsc.getGlobalLabel().trim().equals(""); // insert pinned data and expiry info switch (jobTemplate.getJobType()) { case VERIFY_COMMIT: case VERIFY_PR: - vc.put("isPinned", rc.getVerifyPinned()); - vc.put("label", rc.getVerifyLabel()); + vc.put("isPinned", rc.getVerifyPinned() || isGlobalLabel); + vc.put("label", buildLabel(jsc.getGlobalLabel(), isGlobalLabel, rc.getVerifyLabel(), rc.getVerifyPinned())); vc.put("verifyBuildExpiryDays", rc.getVerifyBuildExpiryDays()); vc.put("verifyBuildExpiryNumber", rc.getVerifyBuildExpiryNumber()); break; case PUBLISH: - vc.put("isPinned", rc.getPublishPinned()); - vc.put("label", rc.getPublishLabel()); + vc.put("isPinned", rc.getPublishPinned() || isGlobalLabel); + vc.put("label", buildLabel(jsc.getGlobalLabel(), isGlobalLabel, rc.getPublishLabel(), rc.getPublishPinned())); vc.put("publishBuildExpiryDays", rc.getPublishBuildExpiryDays()); vc.put("publishBuildExpiryNumber", rc.getPublishBuildExpiryNumber()); break; @@ -272,6 +273,21 @@ public static enum JenkinsBuildParamType { // TODO: more? } + private String buildLabel(String globalLabel, boolean isGlobalLabel, String repoLabel, boolean isRepoPinned) { + if (isGlobalLabel && isRepoPinned) { + return globalLabel + " && ( " + repoLabel + " )"; + } else if (!isGlobalLabel && isRepoPinned) { + return repoLabel; + } else if (isGlobalLabel && !isRepoPinned) { + return globalLabel; + } + + // Doesn't actually matter what we return + // In this case, there is no global or repo pin value + // so this value won't be used. + return repoLabel; + } + /** * Appends the shell magics to the build command to make it succeed/fail * properly. diff --git a/src/main/resources/jenkins-publish-job.vm b/src/main/resources/jenkins-publish-job.vm index 5b9d09b..6e5257b 100644 --- a/src/main/resources/jenkins-publish-job.vm +++ b/src/main/resources/jenkins-publish-job.vm @@ -93,7 +93,7 @@ #if ( $isPinned ) - $label + $esc.xml($label) false #else true diff --git a/src/main/resources/jenkins-verify-job.vm b/src/main/resources/jenkins-verify-job.vm index 4f70653..e0b1d64 100644 --- a/src/main/resources/jenkins-verify-job.vm +++ b/src/main/resources/jenkins-verify-job.vm @@ -91,7 +91,7 @@ #if ( $isPinned ) - $label + $esc.xml($label) false #else true diff --git a/src/main/resources/jenkins-verify-pull-request-job.vm b/src/main/resources/jenkins-verify-pull-request-job.vm index 1b2264c..a52d4b3 100644 --- a/src/main/resources/jenkins-verify-pull-request-job.vm +++ b/src/main/resources/jenkins-verify-pull-request-job.vm @@ -114,7 +114,7 @@ #if ( $isPinned ) - $label + $esc.xml($label) false #else true From 02372bfc685fcdfba888106d81e55410a7979c3d Mon Sep 17 00:00:00 2001 From: Jared Shumway Date: Thu, 17 Dec 2015 16:56:40 -0800 Subject: [PATCH 48/54] Delete all old jobs from Jenkins. Adds UI button to Jenkins Admin page to launch cleaning. --- .../stashbot/managers/JenkinsManager.java | 133 ++++++++++++++++++ .../servlet/JenkinsConfigurationServlet.java | 16 +++ .../static/jenkins-configuration-panel.soy | 41 +++++- 3 files changed, 185 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java index 224bce1..cbcd1c0 100644 --- a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java +++ b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java @@ -25,6 +25,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; +import com.offbytwo.jenkins.model.Build; import org.apache.http.client.HttpResponseException; import org.slf4j.Logger; import org.springframework.beans.factory.DisposableBean; @@ -536,6 +537,138 @@ public void updateAllJobs() { } } + /** + * Code to delete jobs in Jenkins that haven't been run recently. + * + * Jobs that have never been run will be ignored. + * + * @author jshumway + */ + class CleanOldJobsVisitor implements Callable { + + private final JenkinsClientManager jcm; + private final JobTemplateManager jtm; + private final ConfigurationPersistenceService cpm; + private final Repository r; + private final Logger log; + private final long age; + + public CleanOldJobsVisitor(JenkinsClientManager jcm, + JobTemplateManager jtm, + ConfigurationPersistenceService cpm, + Repository r, PluginLoggerFactory lf, int age) { + this.jcm = jcm; + this.jtm = jtm; + this.cpm = cpm; + this.r = r; + this.log = lf.getLoggerForThis(this); + this.age = age; + } + + @Override + public Void call() throws Exception { + RepositoryConfiguration rc = cpm + .getRepositoryConfigurationForRepository(r); + + // Do not delete jobs from repositories with 'Preserve Jenkins Job Config' + // marked, as they cannot be automatically recreated. + if (rc.getPreserveJenkinsJobConfig()) + return null; + + List templates = jtm.getJenkinsJobsForRepository(rc); + JenkinsServerConfiguration jsc = cpm + .getJenkinsServerConfiguration(rc.getJenkinsServerName()); + JenkinsServer js = jcm.getJenkinsServer(jsc, rc, r); + Map jobs = js.getJobs(); + + for (JobTemplate template: templates) { + String jobName = template.getBuildNameFor(r, jsc); + Job job = jobs.get(jobName); + + if (job != null && jobOlderThan(job, 1000 * 60 * 60 * 24 * age)) { + log.info("Deleting job " + job.getName() + " from Jenkins: last " + + "job occurred over " + age + " days ago"); + js.deleteJob(job.getName()); + } + } + + return null; + } + + boolean jobOlderThan(Job job, long ageCutoff) { + try { + final Build lastBuild = job.details().getLastBuild(); + + // Consider jobs with no builds to be newer than |ageCutoff| + if (lastBuild == null) + return false; + + final long lastBuildTime = lastBuild.details().getTimestamp(); + final long now = System.currentTimeMillis(); + final long elapsedTime = now - lastBuildTime; + + return elapsedTime > ageCutoff; + } catch (IOException e) { + return false; + } + } + } + + static class RepositoryFuture { + public Repository r; + public Future f; + public RepositoryFuture(Repository repo, Future future) { + this.r = repo; + this.f = future; + } + } + + /** + * For each repo with a Stashbot configuration that does not have + * 'Preserve Jenkins Config' checked, delete job plans from Jenkins + * if the job has not been run in more than |age| days. + * + * If a job has never been run, it will be ignored. It is possible + * that such jobs are brand new and should not be deleted. + * + * @param age the number of days old a job must be to be deleted + */ + public void cleanOldJobs(int age) { + + ExecutorService es = Executors.newCachedThreadPool(); + List repoFutures = new LinkedList(); + + PageRequest pageReq = new PageRequestImpl(0, 500); + Page p = repositoryService.findAll(pageReq); + + while (true) { + for (Repository r : p.getValues()) { + Future f = es.submit(new CleanOldJobsVisitor( + jenkinsClientManager, jtm, cpm, r, lf, age)); + repoFutures.add(new RepositoryFuture(r, f)); + } + if (p.getIsLastPage()) + break; + pageReq = p.getNextPageRequest(); + p = repositoryService.findAll(pageReq); + } + + for (RepositoryFuture repoFuture : repoFutures) { + Repository r = repoFuture.r; + Future f = repoFuture.f; + try { + f.get(); // don't care about return, just catch exceptions + } catch (ExecutionException e) { + log.error( + "Exception while attempting to clean old jobs for repo " + + r.getName() + ": ", + e); + } catch (InterruptedException e) { + log.error("Interrupted: this shouldn't happen", e); + } + } + } + @Override public void destroy() throws Exception { // on a plugin upgrade or whatever, we want to make sure all tasks get diff --git a/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java b/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java index 6519657..36c2a4c 100644 --- a/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java +++ b/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java @@ -102,6 +102,7 @@ public void doGet(HttpServletRequest req, HttpServletResponse res) throws Servle .replaceAll("/delete/?.*$", "") .replaceAll("/reload-all/?.*$", "") .replaceAll("/create-new/?.*$", "") + .replaceAll("/clean-jobs/?.*$", "") .replaceAll("\\?notice=.*$", "") .replaceAll("\\?error=.*$", ""); @@ -122,6 +123,21 @@ public void doGet(HttpServletRequest req, HttpServletResponse res) throws Servle jenkinsManager.createMissingJobs(); res.sendRedirect(relUrl); } + if (parts[1].equals("clean-jobs")) { + String ageAsString = req.getParameter("age"); + int age; + if (ageAsString == null) { + res.sendRedirect(relUrl + "?error=\"Age is required\""); + } else { + try { + age = Integer.decode(ageAsString); + jenkinsManager.cleanOldJobs(age); + res.sendRedirect(relUrl); + } catch (NumberFormatException e) { + res.sendRedirect(relUrl + "?error=\"Age must be a number\""); + } + } + } } String error = req.getParameter("error"); diff --git a/src/main/resources/static/jenkins-configuration-panel.soy b/src/main/resources/static/jenkins-configuration-panel.soy index d8ce3e5..10c8e28 100644 --- a/src/main/resources/static/jenkins-configuration-panel.soy +++ b/src/main/resources/static/jenkins-configuration-panel.soy @@ -315,22 +315,23 @@ {/param} {/call} +

 

+ {call aui.group.group} {param content}

Stashbot Administrative Tasks

{call aui.group.group} {param content} - {call aui.group.item} + {call aui.group.group} {param content} +

Create and Update

+ {call stash.buttons.button} {param id: 'button-create-missing-plans' /} {param buttonText: 'Create Missing Plans' /} {param href: $relUrl + '/create-new/.' /} {/call} - {/param} - {/call} - {call aui.group.item} - {param content} + {call stash.buttons.button} {param id: 'button-update-all-plans' /} {param buttonText: 'Update/Create Plans' /} @@ -338,6 +339,36 @@ {/call} {/param} {/call} + + {call aui.group.group} + {param content} +

Cleaning

+ Clean up Jenkins Jobs that haven't been run in a certain amount of days. + {call aui.form.form} + {param action: $relUrl + '/clean-jobs/' /} + {param method: 'get' /} + {param content} + + {call aui.form.textField} + {param id: 'age' /} + {param labelContent: stash_i18n('stash.web.stash.ageToClean.label', 'Age to Delete') /} + {param value: '90' /} + {/call} + + {call aui.form.buttons} + {param content} + {call aui.form.submit} + {param id: 'submit' /} + {param text: 'Clean Jobs' /} + {param type: 'submit' /} + {/call} + {/param} + {/call} + + {/param} + {/call} + {/param} + {/call} {/param} {/call} {/param} From aa5558658a457d851f5fcb3143f3bfd29b186355 Mon Sep 17 00:00:00 2001 From: Jared Shumway Date: Sun, 7 Feb 2016 20:39:59 -0800 Subject: [PATCH 49/54] Add dry_run flag to clean-jobs endpoint --- .../stash/stashbot/managers/JenkinsManager.java | 13 +++++++++---- .../servlet/JenkinsConfigurationServlet.java | 12 ++++++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java index 8251657..0f39fbb 100644 --- a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java +++ b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java @@ -566,17 +566,20 @@ class CleanOldJobsVisitor implements Callable { private final Repository r; private final Logger log; private final long age; + private final boolean dryRun; public CleanOldJobsVisitor(JenkinsClientManager jcm, JobTemplateManager jtm, ConfigurationPersistenceService cpm, - Repository r, PluginLoggerFactory lf, int age) { + Repository r, PluginLoggerFactory lf, int age, + boolean dryRun) { this.jcm = jcm; this.jtm = jtm; this.cpm = cpm; this.r = r; this.log = lf.getLoggerForThis(this); this.age = age; + this.dryRun = dryRun; } @Override @@ -602,7 +605,9 @@ public Void call() throws Exception { if (job != null && jobOlderThan(job, 1000 * 60 * 60 * 24 * age)) { log.info("Deleting job " + job.getName() + " from Jenkins: last " + "job occurred over " + age + " days ago"); - js.deleteJob(job.getName()); + if (!dryRun) { + js.deleteJob(job.getName()); + } } } @@ -647,7 +652,7 @@ public RepositoryFuture(Repository repo, Future future) { * * @param age the number of days old a job must be to be deleted */ - public void cleanOldJobs(int age) { + public void cleanOldJobs(int age, boolean dryRun) { ExecutorService es = Executors.newCachedThreadPool(); List repoFutures = new LinkedList(); @@ -658,7 +663,7 @@ public void cleanOldJobs(int age) { while (true) { for (Repository r : p.getValues()) { Future f = es.submit(new CleanOldJobsVisitor( - jenkinsClientManager, jtm, cpm, r, lf, age)); + jenkinsClientManager, jtm, cpm, r, lf, age, dryRun)); repoFutures.add(new RepositoryFuture(r, f)); } if (p.getIsLastPage()) diff --git a/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java b/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java index 36c2a4c..979b1a9 100644 --- a/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java +++ b/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java @@ -124,14 +124,22 @@ public void doGet(HttpServletRequest req, HttpServletResponse res) throws Servle res.sendRedirect(relUrl); } if (parts[1].equals("clean-jobs")) { - String ageAsString = req.getParameter("age"); int age; + boolean dryRun = true; + + String dryRunAsString = req.getParameter("dry_run"); + if (dryRunAsString == null) { + dryRun = false; + } + + String ageAsString = req.getParameter("age"); + if (ageAsString == null) { res.sendRedirect(relUrl + "?error=\"Age is required\""); } else { try { age = Integer.decode(ageAsString); - jenkinsManager.cleanOldJobs(age); + jenkinsManager.cleanOldJobs(age, dryRun); res.sendRedirect(relUrl); } catch (NumberFormatException e) { res.sendRedirect(relUrl + "?error=\"Age must be a number\""); From c3b5502aebd4a19946b55977c17f5b94b431b2c2 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Tue, 1 Mar 2016 14:14:49 -0800 Subject: [PATCH 50/54] Add a dry run option for the UI -Make it the defautlt! -Add extra info to logging to know it's a dry run (no need to scare log viewing people) --- .../stash/stashbot/managers/JenkinsManager.java | 11 +++++++++-- .../stashbot/servlet/JenkinsConfigurationServlet.java | 2 +- .../resources/static/jenkins-configuration-panel.soy | 9 +++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java index 0f39fbb..b93142c 100644 --- a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java +++ b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java @@ -25,7 +25,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; -import com.offbytwo.jenkins.model.Build; import org.apache.http.client.HttpResponseException; import org.slf4j.Logger; import org.springframework.beans.factory.DisposableBean; @@ -44,6 +43,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import com.offbytwo.jenkins.JenkinsServer; +import com.offbytwo.jenkins.model.Build; import com.offbytwo.jenkins.model.Job; import com.palantir.stash.stashbot.config.ConfigurationPersistenceService; import com.palantir.stash.stashbot.jobtemplate.JenkinsJobXmlFormatter; @@ -598,12 +598,13 @@ public Void call() throws Exception { JenkinsServer js = jcm.getJenkinsServer(jsc, rc, r); Map jobs = js.getJobs(); + String dryRunMessage = dryRun ? " [DryRun] " : " "; for (JobTemplate template: templates) { String jobName = template.getBuildNameFor(r, jsc); Job job = jobs.get(jobName); if (job != null && jobOlderThan(job, 1000 * 60 * 60 * 24 * age)) { - log.info("Deleting job " + job.getName() + " from Jenkins: last " + + log.info("Deleting" + dryRunMessage + "job " + job.getName() + " from Jenkins: last " + "job occurred over " + age + " days ago"); if (!dryRun) { js.deleteJob(job.getName()); @@ -657,6 +658,12 @@ public void cleanOldJobs(int age, boolean dryRun) { ExecutorService es = Executors.newCachedThreadPool(); List repoFutures = new LinkedList(); + if (dryRun) { + log.info("Starting clean jobs job. Dry Run."); + } else { + log.info("Starting clean jobs job."); + } + PageRequest pageReq = new PageRequestImpl(0, 500); Page p = repositoryService.findAll(pageReq); diff --git a/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java b/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java index 979b1a9..34b4ded 100644 --- a/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java +++ b/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java @@ -128,7 +128,7 @@ public void doGet(HttpServletRequest req, HttpServletResponse res) throws Servle boolean dryRun = true; String dryRunAsString = req.getParameter("dry_run"); - if (dryRunAsString == null) { + if (dryRunAsString == null || dryRunAsString.trim().equals("") || !dryRunAsString.equals("on")) { dryRun = false; } diff --git a/src/main/resources/static/jenkins-configuration-panel.soy b/src/main/resources/static/jenkins-configuration-panel.soy index e52088c..51077fa 100644 --- a/src/main/resources/static/jenkins-configuration-panel.soy +++ b/src/main/resources/static/jenkins-configuration-panel.soy @@ -367,6 +367,15 @@ {param value: '90' /} {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.cleanjobsdryrun.button.legend', 'Dry Run') /} + {param fields: [[ + 'id': 'dry_run', + 'labelText': stash_i18n('stash.web.stash.locked.button.label', 'Enabled'), + 'isChecked': true + ]] /} + {/call} + {call aui.form.buttons} {param content} {call aui.form.submit} From a980bd63d729cc46366885d0d8cb842f0ce282b8 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Wed, 2 Mar 2016 21:20:22 -0800 Subject: [PATCH 51/54] Add an ending log message --- .../com/palantir/stash/stashbot/managers/JenkinsManager.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java index b93142c..d62da73 100644 --- a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java +++ b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java @@ -693,6 +693,8 @@ public void cleanOldJobs(int age, boolean dryRun) { log.error("Interrupted: this shouldn't happen", e); } } + + log.info("Ending clean jobs job."); } @Override From 08fcae474817f3305f02b3764142f8e523d7a215 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Wed, 2 Mar 2016 21:22:53 -0800 Subject: [PATCH 52/54] Add "unused job" deletion --- .../stashbot/managers/JenkinsManager.java | 27 ++++++++++++------- .../servlet/JenkinsConfigurationServlet.java | 8 +++++- .../static/jenkins-configuration-panel.soy | 10 +++++++ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java index d62da73..a945e6b 100644 --- a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java +++ b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java @@ -567,12 +567,13 @@ class CleanOldJobsVisitor implements Callable { private final Logger log; private final long age; private final boolean dryRun; + private final boolean deleteUnused; public CleanOldJobsVisitor(JenkinsClientManager jcm, JobTemplateManager jtm, ConfigurationPersistenceService cpm, Repository r, PluginLoggerFactory lf, int age, - boolean dryRun) { + boolean dryRun, boolean deleteUnused) { this.jcm = jcm; this.jtm = jtm; this.cpm = cpm; @@ -580,6 +581,7 @@ public CleanOldJobsVisitor(JenkinsClientManager jcm, this.log = lf.getLoggerForThis(this); this.age = age; this.dryRun = dryRun; + this.deleteUnused = deleteUnused; } @Override @@ -603,9 +605,13 @@ public Void call() throws Exception { String jobName = template.getBuildNameFor(r, jsc); Job job = jobs.get(jobName); - if (job != null && jobOlderThan(job, 1000 * 60 * 60 * 24 * age)) { - log.info("Deleting" + dryRunMessage + "job " + job.getName() + " from Jenkins: last " + - "job occurred over " + age + " days ago"); + if (job != null && jobOlderThan(job, 1000 * 60 * 60 * 24 * age, deleteUnused)) { + String deleteMessage = "Deleting" + dryRunMessage + "job " + job.getName() + " from Jenkins: last " + + "job occurred over " + age + " days ago"; + if (deleteUnused) { + deleteMessage += " (or because it was never built)"; + } + log.info(deleteMessage); if (!dryRun) { js.deleteJob(job.getName()); } @@ -615,13 +621,14 @@ public Void call() throws Exception { return null; } - boolean jobOlderThan(Job job, long ageCutoff) { + boolean jobOlderThan(Job job, long ageCutoff, boolean deleteUnused) { try { final Build lastBuild = job.details().getLastBuild(); - // Consider jobs with no builds to be newer than |ageCutoff| - if (lastBuild == null) - return false; + // Consider jobs with no builds to be newer than |ageCutoff| (unless delete unused is set) + if (lastBuild == null) { + return deleteUnused; + } final long lastBuildTime = lastBuild.details().getTimestamp(); final long now = System.currentTimeMillis(); @@ -653,7 +660,7 @@ public RepositoryFuture(Repository repo, Future future) { * * @param age the number of days old a job must be to be deleted */ - public void cleanOldJobs(int age, boolean dryRun) { + public void cleanOldJobs(int age, boolean dryRun, boolean deleteUnused) { ExecutorService es = Executors.newCachedThreadPool(); List repoFutures = new LinkedList(); @@ -670,7 +677,7 @@ public void cleanOldJobs(int age, boolean dryRun) { while (true) { for (Repository r : p.getValues()) { Future f = es.submit(new CleanOldJobsVisitor( - jenkinsClientManager, jtm, cpm, r, lf, age, dryRun)); + jenkinsClientManager, jtm, cpm, r, lf, age, dryRun, deleteUnused)); repoFutures.add(new RepositoryFuture(r, f)); } if (p.getIsLastPage()) diff --git a/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java b/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java index 34b4ded..4eedbee 100644 --- a/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java +++ b/src/main/java/com/palantir/stash/stashbot/servlet/JenkinsConfigurationServlet.java @@ -126,12 +126,18 @@ public void doGet(HttpServletRequest req, HttpServletResponse res) throws Servle if (parts[1].equals("clean-jobs")) { int age; boolean dryRun = true; + boolean deleteUnused = true; String dryRunAsString = req.getParameter("dry_run"); if (dryRunAsString == null || dryRunAsString.trim().equals("") || !dryRunAsString.equals("on")) { dryRun = false; } + String deleteUnusedAsString = req.getParameter("delete_unused"); + if (deleteUnusedAsString == null || deleteUnusedAsString.trim().equals("") || !deleteUnusedAsString.equals("on")) { + deleteUnused = false; + } + String ageAsString = req.getParameter("age"); if (ageAsString == null) { @@ -139,7 +145,7 @@ public void doGet(HttpServletRequest req, HttpServletResponse res) throws Servle } else { try { age = Integer.decode(ageAsString); - jenkinsManager.cleanOldJobs(age, dryRun); + jenkinsManager.cleanOldJobs(age, dryRun, deleteUnused); res.sendRedirect(relUrl); } catch (NumberFormatException e) { res.sendRedirect(relUrl + "?error=\"Age must be a number\""); diff --git a/src/main/resources/static/jenkins-configuration-panel.soy b/src/main/resources/static/jenkins-configuration-panel.soy index 51077fa..b675aa1 100644 --- a/src/main/resources/static/jenkins-configuration-panel.soy +++ b/src/main/resources/static/jenkins-configuration-panel.soy @@ -376,6 +376,16 @@ ]] /} {/call} + {call aui.form.checkboxField} + {param legendContent: stash_i18n('stash.web.stash.cleanjobsdeleteunused.button.legend', 'Include Unused Jobs') /} + {param descriptionText: 'Including unused builds means that any builds that have never run are deleted.' /} + {param fields: [[ + 'id': 'delete_unused', + 'labelText': stash_i18n('stash.web.stash.cleanjobsdeketeunused.button.label', 'Enabled'), + 'isChecked': false + ]] /} + {/call} + {call aui.form.buttons} {param content} {call aui.form.submit} From 1d8a348927ce72a102f76ef9a4d98ce4876c4222 Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Thu, 3 Mar 2016 11:54:29 -0800 Subject: [PATCH 53/54] Fix i8n property label. Bad copy/paste --- src/main/resources/static/jenkins-configuration-panel.soy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/static/jenkins-configuration-panel.soy b/src/main/resources/static/jenkins-configuration-panel.soy index b675aa1..ee95fd4 100644 --- a/src/main/resources/static/jenkins-configuration-panel.soy +++ b/src/main/resources/static/jenkins-configuration-panel.soy @@ -371,7 +371,7 @@ {param legendContent: stash_i18n('stash.web.stash.cleanjobsdryrun.button.legend', 'Dry Run') /} {param fields: [[ 'id': 'dry_run', - 'labelText': stash_i18n('stash.web.stash.locked.button.label', 'Enabled'), + 'labelText': stash_i18n('stash.web.stash.cleanjobsdryrun.button.label', 'Enabled'), 'isChecked': true ]] /} {/call} From f22b0685fb6474e7cb242379202ad83ace3c369a Mon Sep 17 00:00:00 2001 From: Konstantinos Niktas Date: Wed, 2 Mar 2016 21:33:12 -0800 Subject: [PATCH 54/54] Include "unbuilt jobs" in the start message. --- .../palantir/stash/stashbot/managers/JenkinsManager.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java index a945e6b..503ca06 100644 --- a/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java +++ b/src/main/java/com/palantir/stash/stashbot/managers/JenkinsManager.java @@ -665,11 +665,14 @@ public void cleanOldJobs(int age, boolean dryRun, boolean deleteUnused) { ExecutorService es = Executors.newCachedThreadPool(); List repoFutures = new LinkedList(); + String startMessage = "Starting clean jobs job."; + if (deleteUnused) { + startMessage += " Delete unbuilt jobs."; + } if (dryRun) { - log.info("Starting clean jobs job. Dry Run."); - } else { - log.info("Starting clean jobs job."); + startMessage += " Dry run."; } + log.info(startMessage); PageRequest pageReq = new PageRequestImpl(0, 500); Page p = repositoryService.findAll(pageReq);