#!/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: /"""+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= and that will result in git repos being selected only if the selected repos are listed in 'ssh 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= 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 $ ./cmake/tribits/ci_support/clone-extra-repos.py where 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 " \ " (i.e. \"repo0,repo1,,...\"). When set to empty '' (the default value)" \ " then all repos that match listed in " \ " will be selected. But the repos listed in must always" \ " be a subset of the repos of type selected from" \ " . (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 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 to select from " \ ". 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 (e.g. git@)." \ " If specified, then any git repos with the listed as their" \ " root will only be selected if they are listed with 'R' permissions returned" \ " from 'ssh info'. WARNING: Make sure that you have your" \ " gitoliote SSH registered correctly before using this option by typing" \ " the command 'ssh 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 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 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 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)