github.com/apptainer/singularity@v3.1.1+incompatible/cmd/internal/cli/singularity.go (about) 1 // Copyright (c) 2018, Sylabs Inc. All rights reserved. 2 // This software is licensed under a 3-clause BSD license. Please consult the 3 // LICENSE.md file distributed with the sources of this project regarding your 4 // rights to use or distribute this software. 5 6 package cli 7 8 import ( 9 "errors" 10 "fmt" 11 "os" 12 "os/user" 13 "path" 14 "strings" 15 "text/template" 16 17 "github.com/spf13/cobra" 18 "github.com/spf13/pflag" 19 "github.com/sylabs/singularity/docs" 20 "github.com/sylabs/singularity/internal/pkg/buildcfg" 21 "github.com/sylabs/singularity/internal/pkg/sylog" 22 "github.com/sylabs/singularity/internal/pkg/util/auth" 23 ) 24 25 // Global variables for singularity CLI 26 var ( 27 debug bool 28 silent bool 29 verbose bool 30 quiet bool 31 ) 32 33 var ( 34 // TokenFile holds the path to the sylabs auth token file 35 defaultTokenFile, tokenFile string 36 // authToken holds the sylabs auth token 37 authToken, authWarning string 38 ) 39 40 const ( 41 envPrefix = "SINGULARITY_" 42 ) 43 44 func init() { 45 SingularityCmd.Flags().SetInterspersed(false) 46 SingularityCmd.PersistentFlags().SetInterspersed(false) 47 48 templateFuncs := template.FuncMap{ 49 "TraverseParentsUses": TraverseParentsUses, 50 } 51 cobra.AddTemplateFuncs(templateFuncs) 52 53 SingularityCmd.SetHelpTemplate(docs.HelpTemplate) 54 SingularityCmd.SetUsageTemplate(docs.UseTemplate) 55 56 vt := fmt.Sprintf("%s version {{printf \"%%s\" .Version}}\n", buildcfg.PACKAGE_NAME) 57 SingularityCmd.SetVersionTemplate(vt) 58 59 usr, err := user.Current() 60 if err != nil { 61 sylog.Fatalf("Couldn't determine user home directory: %v", err) 62 } 63 defaultTokenFile = path.Join(usr.HomeDir, ".singularity", "sylabs-token") 64 65 SingularityCmd.Flags().BoolVarP(&debug, "debug", "d", false, "print debugging information (highest verbosity)") 66 SingularityCmd.Flags().BoolVarP(&silent, "silent", "s", false, "only print errors") 67 SingularityCmd.Flags().BoolVarP(&quiet, "quiet", "q", false, "suppress normal output") 68 SingularityCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "print additional information") 69 SingularityCmd.Flags().StringVarP(&tokenFile, "tokenfile", "t", defaultTokenFile, "path to the file holding your sylabs authentication token") 70 71 VersionCmd.Flags().SetInterspersed(false) 72 SingularityCmd.AddCommand(VersionCmd) 73 } 74 75 func setSylogMessageLevel(cmd *cobra.Command, args []string) { 76 var level int 77 78 if debug { 79 level = 5 80 } else if verbose { 81 level = 4 82 } else if quiet { 83 level = -1 84 } else if silent { 85 level = -3 86 } else { 87 level = 1 88 } 89 90 sylog.SetLevel(level) 91 } 92 93 // SingularityCmd is the base command when called without any subcommands 94 var SingularityCmd = &cobra.Command{ 95 TraverseChildren: true, 96 DisableFlagsInUseLine: true, 97 PersistentPreRun: persistentPreRun, 98 RunE: func(cmd *cobra.Command, args []string) error { 99 return errors.New("Invalid command") 100 }, 101 102 Use: docs.SingularityUse, 103 Version: buildcfg.PACKAGE_VERSION, 104 Short: docs.SingularityShort, 105 Long: docs.SingularityLong, 106 Example: docs.SingularityExample, 107 SilenceErrors: true, 108 } 109 110 // ExecuteSingularity adds all child commands to the root command and sets 111 // flags appropriately. This is called by main.main(). It only needs to happen 112 // once to the root command (singularity). 113 func ExecuteSingularity() { 114 defaultEnv := "/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin" 115 116 // backup user PATH 117 userEnv := strings.Join([]string{os.Getenv("PATH"), defaultEnv}, ":") 118 os.Setenv("USER_PATH", userEnv) 119 120 os.Setenv("PATH", defaultEnv) 121 if err := SingularityCmd.Execute(); err != nil { 122 os.Exit(1) 123 } 124 } 125 126 // TraverseParentsUses walks the parent commands and outputs a properly formatted use string 127 func TraverseParentsUses(cmd *cobra.Command) string { 128 if cmd.HasParent() { 129 return TraverseParentsUses(cmd.Parent()) + cmd.Use + " " 130 } 131 132 return cmd.Use + " " 133 } 134 135 // VersionCmd displays installed singularity version 136 var VersionCmd = &cobra.Command{ 137 DisableFlagsInUseLine: true, 138 Run: func(cmd *cobra.Command, args []string) { 139 fmt.Println(buildcfg.PACKAGE_VERSION) 140 }, 141 142 Use: "version", 143 Short: "Show the version for Singularity", 144 } 145 146 func updateFlagsFromEnv(cmd *cobra.Command) { 147 cmd.Flags().VisitAll(handleEnv) 148 } 149 150 func handleEnv(flag *pflag.Flag) { 151 envKeys, ok := flag.Annotations["envkey"] 152 if !ok { 153 return 154 } 155 156 for _, key := range envKeys { 157 val, set := os.LookupEnv(envPrefix + key) 158 if !set { 159 continue 160 } 161 162 updateFn := flagEnvFuncs[flag.Name] 163 updateFn(flag, val) 164 } 165 166 } 167 168 func persistentPreRun(cmd *cobra.Command, args []string) { 169 setSylogMessageLevel(cmd, args) 170 updateFlagsFromEnv(cmd) 171 } 172 173 // sylabsToken process the authentication Token 174 // priority default_file < env < file_flag 175 func sylabsToken(cmd *cobra.Command, args []string) { 176 if val := os.Getenv("SYLABS_TOKEN"); val != "" { 177 authToken = val 178 } 179 if tokenFile != defaultTokenFile { 180 authToken, authWarning = auth.ReadToken(tokenFile) 181 } 182 if authToken == "" { 183 authToken, authWarning = auth.ReadToken(defaultTokenFile) 184 } 185 if authToken == "" && authWarning == auth.WarningTokenFileNotFound { 186 sylog.Warningf("%v : Only pulls of public images will succeed", authWarning) 187 } 188 } 189 190 // envAppend combines command line and environment var into a single argument 191 func envAppend(flag *pflag.Flag, envvar string) { 192 if err := flag.Value.Set(envvar); err != nil { 193 sylog.Warningf("Unable to set %s to environment variable value %s", flag.Name, envvar) 194 } else { 195 flag.Changed = true 196 sylog.Debugf("Update flag Value to: %s", flag.Value) 197 } 198 } 199 200 // envBool sets a bool flag if the CLI option is unset and env var is set 201 func envBool(flag *pflag.Flag, envvar string) { 202 if flag.Changed == true || envvar == "" { 203 return 204 } 205 206 if err := flag.Value.Set(envvar); err != nil { 207 sylog.Debugf("Unable to set flag %s to value %s: %s", flag.Name, envvar, err) 208 if err := flag.Value.Set("true"); err != nil { 209 sylog.Warningf("Unable to set flag %s to value %s: %s", flag.Name, envvar, err) 210 return 211 } 212 } 213 214 flag.Changed = true 215 sylog.Debugf("Set %s Value to: %s", flag.Name, flag.Value) 216 } 217 218 // envStringNSlice writes to a string or slice flag if CLI option/argument 219 // string is unset and env var is set 220 func envStringNSlice(flag *pflag.Flag, envvar string) { 221 if flag.Changed == true { 222 return 223 } 224 225 if err := flag.Value.Set(envvar); err != nil { 226 sylog.Warningf("Unable to set flag %s to value %s: %s", flag.Name, envvar, err) 227 return 228 } 229 230 flag.Changed = true 231 sylog.Debugf("Set %s Value to: %s", flag.Name, flag.Value) 232 } 233 234 type envHandle func(*pflag.Flag, string) 235 236 // map of functions to use to bind flags to environment variables 237 var flagEnvFuncs = map[string]envHandle{ 238 // action flags 239 "bind": envAppend, 240 "home": envStringNSlice, 241 "overlay": envStringNSlice, 242 "scratch": envStringNSlice, 243 "workdir": envStringNSlice, 244 "shell": envStringNSlice, 245 "pwd": envStringNSlice, 246 "hostname": envStringNSlice, 247 "network": envStringNSlice, 248 "network-args": envStringNSlice, 249 "dns": envStringNSlice, 250 "containlibs": envStringNSlice, 251 "security": envStringNSlice, 252 "apply-cgroups": envStringNSlice, 253 "app": envStringNSlice, 254 255 "boot": envBool, 256 "fakeroot": envBool, 257 "cleanenv": envBool, 258 "contain": envBool, 259 "containall": envBool, 260 "nv": envBool, 261 "no-nv": envBool, 262 "writable": envBool, 263 "writable-tmpfs": envBool, 264 "no-home": envBool, 265 "no-init": envBool, 266 267 "pid": envBool, 268 "ipc": envBool, 269 "net": envBool, 270 "uts": envBool, 271 "userns": envBool, 272 273 "keep-privs": envBool, 274 "no-privs": envBool, 275 "add-caps": envStringNSlice, 276 "drop-caps": envStringNSlice, 277 "allow-setuid": envBool, 278 279 // build flags 280 "sandbox": envBool, 281 "section": envStringNSlice, 282 "json": envBool, 283 "name": envStringNSlice, 284 // "writable": envBool, // set above for now 285 "force": envBool, 286 "update": envBool, 287 "notest": envBool, 288 "remote": envBool, 289 "detached": envBool, 290 "builder": envStringNSlice, 291 "library": envStringNSlice, 292 "nohttps": envBool, 293 "no-cleanup": envBool, 294 "tmpdir": envStringNSlice, 295 "docker-username": envStringNSlice, 296 "docker-password": envStringNSlice, 297 "docker-login": envBool, 298 299 // capability flags (and others) 300 "user": envStringNSlice, 301 "group": envStringNSlice, 302 "desc": envBool, 303 "all": envBool, 304 305 // instance flags 306 "signal": envStringNSlice, 307 308 // keys flags 309 "secret": envBool, 310 "url": envStringNSlice, 311 312 // inspect flags 313 "labels": envBool, 314 "deffile": envBool, 315 "runscript": envBool, 316 "test": envBool, 317 "environment": envBool, 318 "helpfile": envBool, 319 }