#!/usr/bin/env python # @HEADER # ************************************************************************ # # TriBITS: Tribal Build, Integrate, and Test System # Copyright 2013 Sandia Corporation # # Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation, # the U.S. Government retains certain rights in this software. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # 3. Neither the name of the Corporation nor the names of the # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "AS IS" AND ANY # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SANDIA CORPORATION OR THE # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # ************************************************************************ # @HEADER from CheckinTestConstants import * from FindGeneralScriptSupport import * from GeneralScriptSupport import * from gitdist import addOptionParserChoiceOption import gitdist # # Constants # g_extraReposTypes = g_knownTribitsTestRepoTypes g_extraReposTypeDefault = "Nightly" g_extraReposTypesDefaulIdx = findInSequence(g_extraReposTypes, g_extraReposTypeDefault) g_extraRerposFileDefault = "cmake/ExtraRepositoriesList.cmake" g_verbosityLevels = [ "none", "minimal", "more", "most" ] g_verbosityLevelDefault = "more" g_verbosityLevelDefaultIdx = findInSequence(g_verbosityLevels, g_verbosityLevelDefault) # # Help message # # This part can be reused in other scripts that are project-specific genericUsageHelp = \ r""" By default, this will clone all the 'Nightly' extra repos that are listed in the file: <projectDir>/"""+g_extraRerposFileDefault+r""" (other repo types can be selected using --extra-repos-type). The list of which repos to clone can be "white-list" selected with the option --extra-repos (see options below for details). Extra repos can in addition be "back-listed" using the option --not-extra-repos. To see the full list of repos that can be cloned, pass in just: --skip-clone --verbosity=more That will print out a table like: ------------------------------------------------------------------------------ | ID | Repo Name | Repo Dir | VC | Repo URL | Category | |----|------------|------------|-----|--------------------------|------------| | 1 | ExtraRepo1 | ExtraRepo1 | GIT | someurl.com.ExtraRepo1 | Continuous | | 2 | ExtraRepo3 | ExtraRepo3 | GIT | someurl3.com:/ExtraRepo3 | Continuous | ------------------------------------------------------------------------------ If the git repo server is using gitolite, one can set --gitolite-root=<gitolite-root> and that will result in git repos being selected only if the selected repos are listed in 'ssh <gitolite-root> info'. This allows one to automatically exclude repos from being cloned that the user has no permissions to clone. NOTE: See warning about the --gitolite-root option below! TIP: After cloning the set of repos, a nice way to interact with the repos is to use the tool 'gitdist'. If your project does not have a version controlled .gitdist.default file, you can generate one using the --create-gitdist-file=<gitdist-file> argument, for example with: --create-gitdist-file=.gitdist This will restrict the list of repos processed by gitdist to just the repos cloned. """ usageHelp = r"""clone_extra_repos.py [options] This script clones one more extra repos listed in a TriBITS ExtraRepositoriesList.cmake file. The standard usage is: $ cd base <projectDir> $ ./cmake/tribits/ci_support/clone-extra-repos.py where <projectDir> is the base TriBITS project dir and base git repo. """ + \ genericUsageHelp # # Helper functions # def injectCmndLineOptionsInParser(clp, gitoliteRootDefault=""): clp.add_option( "--extra-repos", dest="extraRepos", type="string", default="", help="List of names of extra repos to be cloned <extra-repos>" \ " (i.e. \"repo0,repo1,,...\"). When set to empty '' (the default value)" \ " then all repos that match <extra-repos-type> listed in <extra-repos-file>" \ " will be selected. But the repos listed in <extra-repos> must always" \ " be a subset of the repos of type <extra-repos-type> selected from" \ " <extra-repos-file>. (Default '')" ) clp.add_option( "--not-extra-repos", dest="notExtraRepos", type="string", default="", help="List of names of extra repos *NOT* to clone (i.e. \"repo0,repo1,...\")." \ " (Default '')" ) clp.add_option( "--extra-repos-file", dest="extraReposFile", type="string", default=g_extraRerposFileDefault, help="The file path <extra-repos-file> for the ExtraRepositoriesList.cmake file." \ " This can be an absolute or relative path." \ " (Default = '"+g_extraRerposFileDefault+"')") addOptionParserChoiceOption( "--extra-repos-type", "extraReposType", g_extraReposTypes , g_extraReposTypesDefaulIdx, "Type of extra repositories <extra-repos-type> to select from " \ "<extra-repos-file>. When --extra-repos is set, then this argument" \ " is ignored.", clp ) clp.add_option( "--gitolite-root", dest="gitoliteRoot", type="string", default=gitoliteRootDefault, help="Gives the root for a gitolite repos <gitolite-root> (e.g. git@<some-url>)." \ " If specified, then any git repos with the <gitolite-root> listed as their" \ " root will only be selected if they are listed with 'R' permissions returned" \ " from 'ssh <gitolite-root> info'. WARNING: Make sure that you have your" \ " gitoliote SSH registered correctly before using this option by typing" \ " the command 'ssh <gitlite-root> info' and make sure that it does *not*" " ask for a password! (Default = '"+gitoliteRootDefault+"')" ) clp.add_option( "--with-cmake", dest="withCmake", type="string", default="cmake", help="CMake executable to use with cmake -P scripts internally (only set" \ +" by unit testing code). (Default = 'cmake')") addOptionParserChoiceOption( "--verbosity", "verbLevel", g_verbosityLevels, g_verbosityLevelDefaultIdx, "Verbosity of the script (levels are cumulative):" \ " none = no output at all (except for commands with --no-op). " \ " minimal = print script args echo and clone commands." \ " more = print basic repo include/exclude logic and print repo table." \ " most = print output from cmake script called, the output from gitolite," \ " and other detailed info." \ , clp ) clp.add_option( "--do-clone", dest="doClone", action="store_true", help="Do the clone of the selected repos. [default]") clp.add_option( "--skip-clone", dest="doClone", action="store_false", help="Skip the clone of the repos and just show what would be done.", default=True ) clp.add_option( "--do-op", dest="doOp", action="store_true", help="Do the clone of the selected repos. [default]" ) clp.add_option( "--no-op", dest="doOp", action="store_false", help="Skip cloning the repos and just show the clone commands.", default=True ) clp.add_option( "--create-gitdist-file", dest="createGitdistFile", type="string", default="", help="If specified, the file <gitdist-file> will get generated with the list" \ " of git repos (the same list that is cloned with --do-clone)." \ " (Default = '')") clp.add_option( "--show-defaults", dest="showDefaults", action="store_true", help="Show the default option values and do nothing at all.", default=False ) def getCmndLineOptions(): from optparse import OptionParser clp = OptionParser(usage=usageHelp) injectCmndLineOptionsInParser(clp) (options, args) = clp.parse_args() return options def isVerbosityLevel(inOptions, testVerbLevel): requestedVerbLevelInt = findInSequence(g_verbosityLevels, inOptions.verbLevel) testVerbLevelInt = findInSequence(g_verbosityLevels, testVerbLevel) if testVerbLevelInt <= requestedVerbLevelInt: return True return False def fwdCmndLineOptions(inOptions, terminator=""): cmndLineOpts = \ " --extra-repos='"+inOptions.extraRepos+"'"+terminator + \ " --not-extra-repos='"+inOptions.notExtraRepos+"'"+terminator + \ " --extra-repos-file='"+inOptions.extraReposFile+"'"+terminator + \ " --extra-repos-type='"+inOptions.extraReposType+"'"+terminator + \ " --gitolite-root='"+inOptions.gitoliteRoot+"'"+terminator + \ " --with-cmake='"+inOptions.withCmake+"'"+terminator + \ " --verbosity='"+inOptions.verbLevel+"'"+terminator if inOptions.doClone: cmndLineOpts += " --do-clone" + terminator else: cmndLineOpts +=" --skip-clone" + terminator if inOptions.doOp: cmndLineOpts += " --do-op" + terminator else: cmndLineOpts += " --no-op" + terminator cmndLineOpts += \ " --create-gitdist-file='"+inOptions.createGitdistFile+"'"+terminator return cmndLineOpts def echoCmndLineOptions(inOptions): print(fwdCmndLineOptions(inOptions, " \\\n")) def echoCmndLine(inOptions): print("") print("**************************************************************************") print("Script: clone_extra_repos.py \\") echoCmndLineOptions(inOptions) def getHeaderOutputAndExtraReposDictList(rawOutputFromCmakefile): rawOutputFromCmakefile = s(rawOutputFromCmakefile) headerOuput = "" pythonDictListStr = "" processingPythonDict = False for line in rawOutputFromCmakefile.splitlines(): if line == "*** Extra Repositories Python Dictionary": processingPythonDict=True continue if processingPythonDict: pythonDictListStr += (line + "\n") else: headerOuput += (line + "\n") #print("\nheaderOuput:\n\n", headerOuput) #print("\npythonDictListStr = '"+pythonDictListStr+"'") pythonDictList = eval(pythonDictListStr) return (headerOuput, pythonDictList) def getExtraReposDictListFromCmakefile(projectDir, extraReposFile, withCmake, extraReposType=g_extraReposTypeDefault, extraRepos="", tribitsDir=tribitsDir, verbose=True, ): cmnd = "\""+withCmake+"\""+ \ " -DPROJECT_SOURCE_DIR="+projectDir+ \ " -DTRIBITS_BASE_DIR="+tribitsDir+ \ " -DEXTRA_REPOS_FILE="+extraReposFile+ \ " -DENABLE_KNOWN_EXTERNAL_REPOS_TYPE="+extraReposType+ \ " -DEXTRA_REPOS="+extraRepos+\ " -DCHECK_EXTRAREPOS_EXIST=FALSE" # " -DTRIBITS_PROCESS_EXTRAREPOS_LISTS_DEBUG=TRUE" cmnd += \ " -P "+ciSupportDir+"/TribitsGetExtraReposForCheckinTest.cmake" rawOutput = getCmndOutput(cmnd, throwOnError=True, getStdErr=True) (headerOutput, extraReposPytonDictList) = getHeaderOutputAndExtraReposDictList(rawOutput) if verbose: print("\n" + headerOutput) return extraReposPytonDictList # Parse raw 'ssh <gitolite-root> info' output and return list of gitolite # repos. def parseRawSshGitoliteRootInfoOutput(rawSshGitoliteRootInfoOutput, verbose=False): rawSshGitoliteRootInfoOutputList = rawSshGitoliteRootInfoOutput.splitlines() gitoliteSshHeader = rawSshGitoliteRootInfoOutputList[0] if verbose: print(gitoliteSshHeader) gitolioteReposList = [] parsingRepos = False for line in rawSshGitoliteRootInfoOutputList: #print("line = '"+line+"'") # Look for the first blank line and then the next line will be the first # listed repo. if line == "": parsingRepos = True continue # Parse the repos if parsingRepos: # The permissions and the repo name are split by a tab such as: # R W ExtraRepo1 repoSplit = line.split("\t") #print("repoSplit =", repoSplit) if len(repoSplit) == 2: gitolioteReposList.append(repoSplit[1]) return gitolioteReposList # Get the list of gitolite repos from 'ssh <gitolite-root> info'. def getGitoliteReposList(inOptions, trace=False, dumpGitoliteOutput=False): cmnd = "ssh "+inOptions.gitoliteRoot+" info" if trace: "Running: "+cmnd rawSshGitoliteRootInfoOutput = getCmndOutput(cmnd) if dumpGitoliteOutput: print(rawSshGitoliteRootInfoOutput) if (trace and not dumpGitoliteOutput): verbose=True else: verbose=False return parseRawSshGitoliteRootInfoOutput(rawSshGitoliteRootInfoOutput, verbose=verbose) def filterOutNotExtraRepos(extraRepoDictList_in, notExtraRepos, verbose=False): notExtraReposSet = set(notExtraRepos) extraRepoDictList = [] for extraReposDict in extraRepoDictList_in: extraRepoName = extraReposDict["NAME"] if extraRepoName in notExtraReposSet: if verbose: print("Excluding extra repo '"+extraRepoName+"'!") else: extraRepoDictList.append(extraReposDict) return extraRepoDictList def filterOutMissingGitoliteRepos(extraRepoDictList_in, gitoliteRepos, verbose=False): gitoliteReposSet = set(gitoliteRepos) extraRepoDictList = [] for extraReposDict in extraRepoDictList_in: extraRepoName = extraReposDict["NAME"] if not extraRepoName in gitoliteReposSet: if verbose: print("Excluding extra repo '" + extraRepoName + "' not listed by gitolite!") else: extraRepoDictList.append(extraReposDict) return extraRepoDictList def getExtraReposTable(extraRepoDictList): # Get the lists for each column in the table repoIdList = [] repoNameList = [] repoDirList = [] repoVcTypeList = [] repoUrlList = [] repoCategoryList = [] repoId = 1 # Use one-base indexing to match gitdist IDs! for extraRepoDict in extraRepoDictList: repoIdList.append(str(repoId)) repoNameList.append(extraRepoDict["NAME"]) repoDirList.append(extraRepoDict["DIR"]) repoVcTypeList.append(extraRepoDict["REPOTYPE"]) repoUrlList.append(extraRepoDict["REPOURL"]) repoCategoryList.append(extraRepoDict["CATEGORY"]) repoId += 1 # Create the table extraReposTableDictList = [ { "label":"ID", "align":"R", "fields":repoIdList }, { "label":"Repo Name", "align":"L", "fields":repoNameList }, { "label":"Repo Dir", "align":"L", "fields":repoDirList }, { "label":"VC", "align":"L", "fields":repoVcTypeList }, { "label":"Repo URL", "align":"L", "fields":repoUrlList }, { "label":"Category", "align":"L", "fields":repoCategoryList }, ] #print("extraReposTableDictList =", extraReposTableDictList) extraReposTable = gitdist.createTable(extraReposTableDictList) # Return the table return extraReposTable def createGitDistFile(extraRepoDictList, gitdistFile, verbose=False): gitdistFileStr = "" for extraRepoDict in extraRepoDictList: gitdistFileStr += extraRepoDict["DIR"]+"\n" if verbose: print("Writing the file '" + gitdistFile + "' ...") open(gitdistFile, 'w').write(gitdistFileStr) def cloneExtraRepo(inOptions, extraRepoDict): repoName = extraRepoDict["NAME"] repoDir = extraRepoDict["DIR"] repoUrl = extraRepoDict["REPOURL"] repoVcType = extraRepoDict["REPOTYPE"] verbLevelIsMinimum = isVerbosityLevel(inOptions, "minimal") if verbLevelIsMinimum: print("\nCloning repo " + repoName + " ...") if os.path.exists(repoDir): if verbLevelIsMinimum: print("\n ==> Repo dir = '" + repoDir + "' already exists. Skipping clone!") return if repoVcType != "GIT": print("\n ==> ERROR: Repo type '"+repoVcType+"' not supported!") sys.exit(1) cmnd = "git clone "+repoUrl+" "+repoDir if inOptions.doOp: echoRunSysCmnd(cmnd, timeCmnd=True, verbose=verbLevelIsMinimum) elif verbLevelIsMinimum: print("\nRunning: " + cmnd) def cloneExtraRepos(inOptions): verbLevelIsMinimal = isVerbosityLevel(inOptions, "minimal") # # A) Get the list of extra repos # extraRepoDictList = getExtraReposDictListFromCmakefile( projectDir=os.getcwd(), extraReposFile=inOptions.extraReposFile, extraReposType=inOptions.extraReposType, extraRepos=inOptions.extraRepos, withCmake=inOptions.withCmake, verbose=isVerbosityLevel(inOptions, "most") ) #print("extraRepoDictList =" + str(extraRepoDictList)) # # B) Get the list of gitolite repos # if inOptions.gitoliteRoot: if verbLevelIsMinimal: print("\n***") print("*** Get the list of repos that can be cloned from gitolite server:") print("***\n") gitoliteRepos = getGitoliteReposList(inOptions, trace=verbLevelIsMinimal, dumpGitoliteOutput=isVerbosityLevel(inOptions, "most")) else: gitoliteRepos = [] #print("gitoliteRepos =", gitoliteRepos) # # C) Filter the list of extra repos # if gitoliteRepos: if verbLevelIsMinimal: print("\n***") print("*** Filtering the set of extra repos based on gitolite repos:") print("***\n") extraRepoDictList = filterOutMissingGitoliteRepos( extraRepoDictList, gitoliteRepos, verbose=verbLevelIsMinimal) if inOptions.notExtraRepos: if verbLevelIsMinimal: print("\n***") print("*** Filtering the set of extra repos based on --not-extra-repos:") print("***\n") extraRepoDictList = filterOutNotExtraRepos( extraRepoDictList, inOptions.notExtraRepos.split(","), verbose=verbLevelIsMinimal) # # D) print out table of repos # if isVerbosityLevel(inOptions, "more"): print("\n***") print("*** List of selected extra repos to clone:") print("***\n") extraReposTable = getExtraReposTable(extraRepoDictList) print(extraReposTable) # # E) Clone the repos # if inOptions.doClone: if verbLevelIsMinimal: print("\n***") print("*** Clone the selected extra repos:") print("***\n") for extraRepoDict in extraRepoDictList: cloneExtraRepo(inOptions, extraRepoDict) # # F) Create the gitdist file # if inOptions.createGitdistFile: if verbLevelIsMinimal: print("\n***") print("*** Create the gitdist file:") print("***\n") createGitDistFile(extraRepoDictList, inOptions.createGitdistFile, verbose=verbLevelIsMinimal) # # Run the script # if __name__ == '__main__': inOptions = getCmndLineOptions() if isVerbosityLevel(inOptions, "minimal"): echoCmndLine(inOptions) if inOptions.showDefaults: sys.exit(0) cloneExtraRepos(inOptions)