Skip to content

Commit

Permalink
fix rundeck#1624 git remote fetch error should be handled
Browse files Browse the repository at this point in the history
  • Loading branch information
gschueler committed Apr 26, 2016
1 parent 5c0d298 commit f94f668
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand All @@ -192,27 +201,30 @@ 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()) {
//if any paths exist, need to export
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.join(', ')
if (fetchError && synchState.state == SynchState.CLEAN) {
synchState.state = SynchState.REFRESH_NEEDED
}

return synchState
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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==''

}
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
90 changes: 90 additions & 0 deletions rundeckapp/test/unit/rundeck/services/ScmServiceSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String>(['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<String>(['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)
Expand Down

0 comments on commit f94f668

Please sign in to comment.