github.com/htcondor/osdf-client/v6@v6.13.0-rc1.0.20231009141709-766e7b4d1dc8/cmd/stashcp/main.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "net/url" 6 "os" 7 "strings" 8 9 stashcp "github.com/htcondor/osdf-client/v6" 10 namespaces "github.com/htcondor/osdf-client/v6/namespaces" 11 "github.com/jessevdk/go-flags" 12 log "github.com/sirupsen/logrus" 13 ) 14 15 var ( 16 version = "dev" 17 commit = "none" 18 date = "unknown" 19 builtBy = "unknown" 20 ) 21 22 type SourceDestination struct { 23 Sources []string `positional-arg-name:"sources" short:"i" long:"input" description:"Source file(s)" default:"-"` 24 25 // A useless variable. It should alwasy be empty. The Sources variable above should "grab" all of the positional arguments 26 // The only reason we have this variable is so the help message has the [sources...] [destination] help mesage. 27 Destination string `positional-arg-name:"destination" short:"o" long:"output" description:"Destination file/directory" default:"-"` 28 } 29 30 type Options struct { 31 // Turn on the debug logging 32 Debug bool `short:"d" long:"debug" description:"Turn on debug logging"` 33 34 // Specify the configuration file 35 Closest bool `long:"closest" description:"Return the closest cache and exit"` 36 37 // Cache to use 38 Cache string `short:"c" long:"cache" description:"Cache to use"` 39 40 // A JSON file containing the list of caches 41 CacheJSON string `short:"j" long:"caches-json" description:"A JSON file containing the list of caches"` 42 43 // Comma separated list of methods to try, in order. Default: cvmfs,http 44 Methods string `long:"methods" description:"Comma separated list of methods to try, in order." default:"cvmfs,http"` 45 46 // Token file to use for reading and/or writing 47 Token string `long:"token" short:"t" description:"Token file to use for reading and/or writing"` 48 49 ListCaches bool `long:"list-names" description:"Return the names of pre-configured cache lists and exit"` 50 51 ListDir bool `long:"list-dir" short:"l" description:"List the directory pointed to by source"` 52 53 // Version information 54 Version bool `long:"version" short:"v" description:"Print the version and exit"` 55 56 // Namespace information 57 PrintNamespaces bool `long:"namespaces" description:"Print the namespace information and exit"` 58 59 // List Types (xroot or xroots) 60 ListType string `long:"cache-list-name" short:"n" description:"Cache list to use, currently either xroot or xroots" default:"xroot"` 61 62 // Recursive walking of directories 63 Recursive bool `short:"r" description:"Recursively copy a directory. Forces methods to only be http to get the freshest directory contents"` 64 65 // Progress bars 66 ProgessBars bool `long:"progress" short:"p" description:"Show progress bars, turned on if run from a terminal"` 67 68 // PluginInterface specifies how the output should be formatted 69 PluginInterface bool `long:"plugininterface" description:"Output in HTCondor plugin format. Turned on if executable is named stash_plugin"` 70 71 // Positional arguemnts 72 SourceDestination SourceDestination `description:"Source and Destination Files" positional-args:"1"` 73 } 74 75 var options Options 76 77 var parser = flags.NewParser(&options, flags.Default) 78 79 func main() { 80 81 stashcp.Options.Version = version 82 83 // Capture the start time of the transfer 84 if _, err := parser.Parse(); err != nil { 85 if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp { 86 os.Exit(0) 87 } else { 88 log.Errorln(err) 89 os.Exit(1) 90 } 91 } 92 93 if options.Debug { 94 // Set logging to debug level 95 err := setLogging(log.DebugLevel) 96 if err != nil { 97 log.Panicln("Failed to set logging level to Debug:", err) 98 } 99 } else { 100 err := setLogging(log.ErrorLevel) 101 if err != nil { 102 log.Panicln("Failed to set logging level to Error:", err) 103 } 104 105 } 106 107 if options.Version { 108 fmt.Println("Version:", version) 109 fmt.Println("Build Date:", date) 110 fmt.Println("Build Commit:", commit) 111 fmt.Println("Built By:", builtBy) 112 os.Exit(0) 113 } 114 115 // Set the progress bars to the command line option 116 stashcp.Options.ProgressBars = options.ProgessBars 117 stashcp.Options.Token = options.Token 118 119 // Check if the program was executed from a terminal 120 // https://rosettacode.org/wiki/Check_output_device_is_a_terminal#Go 121 if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) != 0 { 122 stashcp.Options.ProgressBars = true 123 } else { 124 stashcp.Options.ProgressBars = false 125 } 126 127 if options.PrintNamespaces { 128 namespaces, err := namespaces.GetNamespaces() 129 if err != nil { 130 fmt.Println("Failed to get namespaces:", err) 131 os.Exit(1) 132 } 133 fmt.Printf("%+v\n", namespaces) 134 os.Exit(0) 135 } 136 137 // Just return all the caches that it knows about 138 // Print out all of the caches and exit 139 if options.ListCaches { 140 cacheList, err := stashcp.GetBestCache(options.ListType) 141 if err != nil { 142 log.Errorln("Failed to get best caches:", err) 143 os.Exit(1) 144 } 145 // Print the caches, comma separated, 146 fmt.Println(strings.Join(cacheList[:], ",")) 147 os.Exit(0) 148 } 149 150 if options.Closest { 151 cacheList, err := stashcp.GetBestCache(options.ListType) 152 if err != nil { 153 log.Errorln("Failed to get best stashcache: ", err) 154 } 155 fmt.Println(cacheList[0]) 156 os.Exit(0) 157 } 158 159 log.Debugln("Len of source:", len(options.SourceDestination.Sources)) 160 if len(options.SourceDestination.Sources) < 2 { 161 log.Errorln("No Source or Destination") 162 parser.WriteHelp(os.Stdout) 163 os.Exit(1) 164 } 165 source := options.SourceDestination.Sources[:len(options.SourceDestination.Sources)-1] 166 dest := options.SourceDestination.Sources[len(options.SourceDestination.Sources)-1] 167 168 log.Debugln("Sources:", source) 169 log.Debugln("Destination:", dest) 170 if options.ListDir { 171 dirUrl, _ := url.Parse("http://stash.osgconnect.net:1094") 172 dirUrl.Path = source[0] 173 isDir, err := stashcp.IsDir(dirUrl, "", namespaces.Namespace{}) 174 if err != nil { 175 log.Errorln("Error getting directory listing:", err) 176 } 177 log.Debugln("Dir is a directory?", isDir) 178 return 179 } 180 181 /* 182 TODO: Parse a caches JSON, is this needed anymore? 183 if args.caches_json { 184 caches_json_location = caches_json 185 186 } else if val, jsonPresent := os.LookupEnv("CACHES_JSON"); jsonPresent { 187 caches_json_location = val 188 } else { 189 prefix = os.Getenv("OSG_LOCATION", "/") 190 caches_file = filepath.Join(prefix, "etc/stashcache/caches.json") 191 if _, err := os.Stat(caches_file); err == nil { 192 caches_json_location = caches_file 193 } 194 } 195 196 caches_list_name = args.cache_list_name 197 */ 198 199 // Check for manually entered cache to use ?? 200 nearestCache, nearestCacheIsPresent := os.LookupEnv("NEAREST_CACHE") 201 202 if nearestCacheIsPresent { 203 stashcp.NearestCache = nearestCache 204 stashcp.NearestCacheList = append(stashcp.NearestCacheList, stashcp.NearestCache) 205 stashcp.CacheOverride = true 206 } else if options.Cache != "" { 207 stashcp.NearestCache = options.Cache 208 stashcp.NearestCacheList = append(stashcp.NearestCacheList, options.Cache) 209 stashcp.CacheOverride = true 210 } 211 212 // Convert the methods 213 splitMethods := strings.Split(options.Methods, ",") 214 215 // If the user overrides the cache, then only use HTTP 216 if stashcp.CacheOverride { 217 splitMethods = []string{"http"} 218 } 219 220 if len(source) > 1 { 221 if destStat, err := os.Stat(dest); err != nil && destStat.IsDir() { 222 log.Errorln("Destination is not a directory") 223 os.Exit(1) 224 } 225 } 226 227 var result error 228 var downloaded int64 = 0 229 lastSrc := "" 230 for _, src := range source { 231 var tmpDownloaded int64 232 tmpDownloaded, result = stashcp.DoStashCPSingle(src, dest, splitMethods, options.Recursive) 233 downloaded += tmpDownloaded 234 if result != nil { 235 lastSrc = src 236 break 237 } else { 238 stashcp.ClearErrors() 239 } 240 } 241 242 // Exit with failure 243 if result != nil { 244 // Print the list of errors 245 errMsg := stashcp.GetErrors() 246 if errMsg == "" { 247 errMsg = result.Error() 248 } 249 log.Errorln("Failure downloading " + lastSrc + ": " + errMsg) 250 if stashcp.ErrorsRetryable() { 251 log.Errorln("Errors are retryable") 252 os.Exit(11) 253 } 254 os.Exit(1) 255 } 256 257 } 258 259 func setLogging(logLevel log.Level) error { 260 textFormatter := log.TextFormatter{} 261 textFormatter.DisableLevelTruncation = true 262 textFormatter.FullTimestamp = true 263 log.SetFormatter(&textFormatter) 264 log.SetLevel(logLevel) 265 return nil 266 }