diff --git a/plugins/git-plugin/src/main/groovy/org/rundeck/plugin/scm/git/GitExportPlugin.groovy b/plugins/git-plugin/src/main/groovy/org/rundeck/plugin/scm/git/GitExportPlugin.groovy index 61e3b62c69c..6aae0803042 100644 --- a/plugins/git-plugin/src/main/groovy/org/rundeck/plugin/scm/git/GitExportPlugin.groovy +++ b/plugins/git-plugin/src/main/groovy/org/rundeck/plugin/scm/git/GitExportPlugin.groovy @@ -173,8 +173,17 @@ class GitExportPlugin extends BaseGitPlugin implements ScmExportPlugin { GitExportSynchState getStatusInternal(ScmOperationContext context, boolean performFetch) { //perform fetch + def msgs=[] + boolean fetchError=false if (performFetch) { - fetchFromRemote(context) + try { + fetchFromRemote(context) + } catch (Exception e) { + fetchError=true + msgs<<"Fetch from the repository failed: ${e.message}" + logger.error("Failed fetch from the repository: ${e.message}") + logger.debug("Failed fetch from the repository: ${e.message}", e) + } } Status status = git.status().call() @@ -183,7 +192,7 @@ class GitExportPlugin extends BaseGitPlugin implements ScmExportPlugin { synchState.gitStatus = status synchState.state = status.isClean() ? SynchState.CLEAN : SynchState.EXPORT_NEEDED if (!status.isClean()) { - synchState.message = "Some changes have not been committed" + msgs<< "Some changes have not been committed" } //if clean, check remote tracking status @@ -192,14 +201,14 @@ class GitExportPlugin extends BaseGitPlugin implements ScmExportPlugin { if (bstat) { synchState.branchTrackingStatus = bstat if (bstat && bstat.aheadCount > 0 && bstat.behindCount > 0) { - synchState.message = "${bstat.aheadCount} ahead and ${bstat.behindCount} behind remote branch" + msgs<< "${bstat.aheadCount} ahead and ${bstat.behindCount} behind remote branch" synchState.state = SynchState.REFRESH_NEEDED //TODO: test if merge would fail } else if (bstat && bstat.aheadCount > 0) { - synchState.message = "${bstat.aheadCount} changes need to be pushed" + msgs<< "${bstat.aheadCount} changes need to be pushed" synchState.state = SynchState.EXPORT_NEEDED } else if (bstat && bstat.behindCount > 0) { - synchState.message = "${bstat.behindCount} changes from remote need to be pulled" + msgs<< "${bstat.behindCount} changes from remote need to be pulled" synchState.state = SynchState.REFRESH_NEEDED } } else if (!remoteTrackingBranch()) { @@ -207,12 +216,15 @@ class GitExportPlugin extends BaseGitPlugin implements ScmExportPlugin { def head = GitUtil.getHead(git.repository) if (head) { //if no remote branch exists, i.e. bare repo, need to push local files - synchState.message = "Changes need to be pushed" + msgs<< "Changes need to be pushed" synchState.state = SynchState.EXPORT_NEEDED } } } - + synchState.message=msgs? msgs.join(', ') : null + if (fetchError && synchState.state == SynchState.CLEAN) { + synchState.state = SynchState.REFRESH_NEEDED + } return synchState } diff --git a/plugins/git-plugin/src/main/groovy/org/rundeck/plugin/scm/git/GitImportPlugin.groovy b/plugins/git-plugin/src/main/groovy/org/rundeck/plugin/scm/git/GitImportPlugin.groovy index 18756fa2332..618aeed10f9 100644 --- a/plugins/git-plugin/src/main/groovy/org/rundeck/plugin/scm/git/GitImportPlugin.groovy +++ b/plugins/git-plugin/src/main/groovy/org/rundeck/plugin/scm/git/GitImportPlugin.groovy @@ -122,8 +122,15 @@ class GitImportPlugin extends BaseGitPlugin implements ScmImportPlugin { return null } + def msgs = [] if (performFetch) { - fetchFromRemote(context) + try { + fetchFromRemote(context) + } catch (Exception e) { + msgs<<"Fetch from the repository failed: ${e.message}" + logger.error("Failed fetch from the repository: ${e.message}") + logger.debug("Failed fetch from the repository: ${e.message}", e) + } } int importNeeded = 0 @@ -173,7 +180,6 @@ class GitImportPlugin extends BaseGitPlugin implements ScmImportPlugin { } else { state.state = ImportSynchState.CLEAN } - def msgs = [] if (bstat && bstat.behindCount > 0) { msgs << "${bstat.behindCount} changes from remote need to be pulled" diff --git a/plugins/git-plugin/src/test/groovy/org/rundeck/plugin/scm/git/GitExportPluginSpec.groovy b/plugins/git-plugin/src/test/groovy/org/rundeck/plugin/scm/git/GitExportPluginSpec.groovy index 6ef035ee290..0678880e924 100644 --- a/plugins/git-plugin/src/test/groovy/org/rundeck/plugin/scm/git/GitExportPluginSpec.groovy +++ b/plugins/git-plugin/src/test/groovy/org/rundeck/plugin/scm/git/GitExportPluginSpec.groovy @@ -315,6 +315,52 @@ class GitExportPluginSpec extends Specification { } + def "get status clean"() { + given: + + def gitdir = new File(tempdir, 'scm') + def origindir = new File(tempdir, 'origin') + Export config = createTestConfig(gitdir, origindir) + + //create a git dir + def git = createGit(origindir) + git.close() + def plugin = new GitExportPlugin(config) + plugin.initialize(Mock(ScmOperationContext)) + + when: + def status = plugin.getStatus(Mock(ScmOperationContext)) + + then: + status!=null + status.state==SynchState.CLEAN + status.message==null + + } + def "get status fetch fails"() { + given: + + def gitdir = new File(tempdir, 'scm') + def origindir = new File(tempdir, 'origin') + Export config = createTestConfig(gitdir, origindir) + + //create a git dir + def git = createGit(origindir) + git.close() + def plugin = new GitExportPlugin(config) + plugin.initialize(Mock(ScmOperationContext)) + + //delete origin contents, will cause fetch to fail + FileUtils.delete(origindir, FileUtils.RECURSIVE) + + when: + def status = plugin.getStatus(Mock(ScmOperationContext)) + + then: + status!=null + status.state==SynchState.REFRESH_NEEDED + status.message=='Fetch from the repository failed: Invalid remote: origin' + } static RevCommit addCommitFile(final File gitdir, final Git git, final String path, final String content) { def outfile = new File(gitdir, path) diff --git a/rundeckapp/grails-app/services/rundeck/services/ScmService.groovy b/rundeckapp/grails-app/services/rundeck/services/ScmService.groovy index 043bf762d2a..d80b3719a19 100644 --- a/rundeckapp/grails-app/services/rundeck/services/ScmService.groovy +++ b/rundeckapp/grails-app/services/rundeck/services/ScmService.groovy @@ -868,7 +868,11 @@ class ScmService { ScmExportSynchState exportPluginStatus(UserAndRolesAuthContext auth, String project) throws ScmPluginException { def plugin = getLoadedExportPluginFor project if (plugin) { - return plugin.getStatus(scmOperationContext(auth, project)) + try{ + return plugin.getStatus(scmOperationContext(auth, project)) + }catch (Throwable t){ + log.error("Failed to get status for SCM export plugin in project ${project}: $t",t); + } } null } diff --git a/rundeckapp/test/unit/rundeck/services/ScmServiceSpec.groovy b/rundeckapp/test/unit/rundeck/services/ScmServiceSpec.groovy index e077649534a..963b17a2b31 100644 --- a/rundeckapp/test/unit/rundeck/services/ScmServiceSpec.groovy +++ b/rundeckapp/test/unit/rundeck/services/ScmServiceSpec.groovy @@ -10,6 +10,7 @@ import com.dtolabs.rundeck.plugins.scm.ScmCommitInfo import com.dtolabs.rundeck.plugins.scm.ScmExportPlugin import com.dtolabs.rundeck.plugins.scm.ScmExportPluginFactory import com.dtolabs.rundeck.plugins.scm.ScmExportResult +import com.dtolabs.rundeck.plugins.scm.ScmExportSynchState import com.dtolabs.rundeck.plugins.scm.ScmImportPlugin import com.dtolabs.rundeck.plugins.scm.ScmImportPluginFactory import com.dtolabs.rundeck.plugins.scm.ScmOperationContext @@ -323,6 +324,95 @@ class ScmServiceSpec extends Specification { ScmService.EXPORT | _ } + + def "export project status basic"() { + given: + def ctx = Mock(ScmOperationContext) { + getFrameworkProject() >> 'testProject' + } + def config = [:] + + ScmExportPluginFactory exportFactory = Mock(ScmExportPluginFactory) + ScmExportPlugin plugin = Mock(ScmExportPlugin) + + service.pluginService = Mock(PluginService) + service.pluginConfigService = Mock(PluginConfigService) + service.jobEventsService = Mock(JobEventsService) + service.frameworkService = Mock(FrameworkService) + service.storageService = Mock(StorageService) + + def validated = new ValidatedPlugin(valid: true) + + when: + def result = service.exportPluginStatus(Mock(UserAndRolesAuthContext),'testProject') + + then: + 1 * service.pluginConfigService.loadScmConfig('testProject', 'etc/scm-export.properties', 'scm.export')>>Mock(ScmPluginConfigData){ + 1 * getEnabled()>>true + 1 * getSetting('username') >> 'testuser' + 1 * getSettingList('roles') >> ['arole'] + getType()>>'atype' + getConfig()>>config + } + service.frameworkService.getAuthContextForUserAndRoles(_,_)>>Mock(UserAndRolesAuthContext){ + getUsername()>>'testuser' + getRoles()>>new HashSet(['arole']) + } + 1 * service.frameworkService.getFrameworkPropertyResolver(*_) + 1 * service.pluginService.validatePlugin(*_) >> validated + 1 * service.pluginService.getPlugin('atype', _) >> exportFactory + 1 * exportFactory.createPlugin(_, config) >> plugin + 1 * service.jobEventsService.addListenerForProject(_, 'testProject') + 1 * plugin.getStatus(_)>>Mock(ScmExportSynchState) + + result != null + } + + + def "export project status exception"() { + given: + def ctx = Mock(ScmOperationContext) { + getFrameworkProject() >> 'testProject' + } + def config = [:] + + ScmExportPluginFactory exportFactory = Mock(ScmExportPluginFactory) + ScmExportPlugin plugin = Mock(ScmExportPlugin) + + service.pluginService = Mock(PluginService) + service.pluginConfigService = Mock(PluginConfigService) + service.jobEventsService = Mock(JobEventsService) + service.frameworkService = Mock(FrameworkService) + service.storageService = Mock(StorageService) + + def validated = new ValidatedPlugin(valid: true) + + when: + def result = service.exportPluginStatus(Mock(UserAndRolesAuthContext),'testProject') + + then: + 1 * service.pluginConfigService.loadScmConfig('testProject', 'etc/scm-export.properties', 'scm.export')>>Mock(ScmPluginConfigData){ + 1 * getEnabled()>>true + 1 * getSetting('username') >> 'testuser' + 1 * getSettingList('roles') >> ['arole'] + getType()>>'atype' + getConfig()>>config + } + service.frameworkService.getAuthContextForUserAndRoles(_,_)>>Mock(UserAndRolesAuthContext){ + getUsername()>>'testuser' + getRoles()>>new HashSet(['arole']) + } + 1 * service.frameworkService.getFrameworkPropertyResolver(*_) + 1 * service.pluginService.validatePlugin(*_) >> validated + 1 * service.pluginService.getPlugin('atype', _) >> exportFactory + 1 * exportFactory.createPlugin(_, config) >> plugin + 1 * service.jobEventsService.addListenerForProject(_, 'testProject') + 1 * plugin.getStatus(_)>>{ + throw new RuntimeException("get status failed") + } + result == null + } + def "perform export plugin action should store commit metadata into job import metadata"() { given: service.jobMetadataService = Mock(JobMetadataService)