github.com/apptainer/singularity@v3.1.1+incompatible/cmd/internal/cli/actions.go (about) 1 // Copyright (c) 2018-2019, 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 "fmt" 10 "strings" 11 12 ocitypes "github.com/containers/image/types" 13 "github.com/spf13/cobra" 14 "github.com/sylabs/singularity/docs" 15 "github.com/sylabs/singularity/internal/pkg/build" 16 "github.com/sylabs/singularity/internal/pkg/client/cache" 17 ociclient "github.com/sylabs/singularity/internal/pkg/client/oci" 18 "github.com/sylabs/singularity/internal/pkg/libexec" 19 "github.com/sylabs/singularity/internal/pkg/sylog" 20 "github.com/sylabs/singularity/internal/pkg/util/uri" 21 "github.com/sylabs/singularity/pkg/build/types" 22 library "github.com/sylabs/singularity/pkg/client/library" 23 ) 24 25 func init() { 26 actionCmds := []*cobra.Command{ 27 ExecCmd, 28 ShellCmd, 29 RunCmd, 30 TestCmd, 31 } 32 33 // TODO : the next n lines of code are repeating too much but I don't 34 // know how to shorten them tonight 35 for _, cmd := range actionCmds { 36 cmd.Flags().AddFlag(actionFlags.Lookup("bind")) 37 cmd.Flags().AddFlag(actionFlags.Lookup("contain")) 38 cmd.Flags().AddFlag(actionFlags.Lookup("containall")) 39 cmd.Flags().AddFlag(actionFlags.Lookup("cleanenv")) 40 cmd.Flags().AddFlag(actionFlags.Lookup("home")) 41 cmd.Flags().AddFlag(actionFlags.Lookup("ipc")) 42 cmd.Flags().AddFlag(actionFlags.Lookup("net")) 43 cmd.Flags().AddFlag(actionFlags.Lookup("network")) 44 cmd.Flags().AddFlag(actionFlags.Lookup("network-args")) 45 cmd.Flags().AddFlag(actionFlags.Lookup("dns")) 46 cmd.Flags().AddFlag(actionFlags.Lookup("nv")) 47 cmd.Flags().AddFlag(actionFlags.Lookup("overlay")) 48 cmd.Flags().AddFlag(actionFlags.Lookup("pid")) 49 cmd.Flags().AddFlag(actionFlags.Lookup("uts")) 50 cmd.Flags().AddFlag(actionFlags.Lookup("pwd")) 51 cmd.Flags().AddFlag(actionFlags.Lookup("scratch")) 52 cmd.Flags().AddFlag(actionFlags.Lookup("userns")) 53 cmd.Flags().AddFlag(actionFlags.Lookup("workdir")) 54 cmd.Flags().AddFlag(actionFlags.Lookup("hostname")) 55 cmd.Flags().AddFlag(actionFlags.Lookup("fakeroot")) 56 cmd.Flags().AddFlag(actionFlags.Lookup("keep-privs")) 57 cmd.Flags().AddFlag(actionFlags.Lookup("no-privs")) 58 cmd.Flags().AddFlag(actionFlags.Lookup("add-caps")) 59 cmd.Flags().AddFlag(actionFlags.Lookup("drop-caps")) 60 cmd.Flags().AddFlag(actionFlags.Lookup("allow-setuid")) 61 cmd.Flags().AddFlag(actionFlags.Lookup("writable")) 62 cmd.Flags().AddFlag(actionFlags.Lookup("writable-tmpfs")) 63 cmd.Flags().AddFlag(actionFlags.Lookup("no-home")) 64 cmd.Flags().AddFlag(actionFlags.Lookup("no-init")) 65 cmd.Flags().AddFlag(actionFlags.Lookup("security")) 66 cmd.Flags().AddFlag(actionFlags.Lookup("apply-cgroups")) 67 cmd.Flags().AddFlag(actionFlags.Lookup("app")) 68 cmd.Flags().AddFlag(actionFlags.Lookup("containlibs")) 69 cmd.Flags().AddFlag(actionFlags.Lookup("no-nv")) 70 cmd.Flags().AddFlag(actionFlags.Lookup("tmpdir")) 71 cmd.Flags().AddFlag(actionFlags.Lookup("nohttps")) 72 cmd.Flags().AddFlag(actionFlags.Lookup("docker-username")) 73 cmd.Flags().AddFlag(actionFlags.Lookup("docker-password")) 74 cmd.Flags().AddFlag(actionFlags.Lookup("docker-login")) 75 if cmd == ShellCmd { 76 cmd.Flags().AddFlag(actionFlags.Lookup("shell")) 77 } 78 cmd.Flags().SetInterspersed(false) 79 } 80 81 SingularityCmd.AddCommand(ExecCmd) 82 SingularityCmd.AddCommand(ShellCmd) 83 SingularityCmd.AddCommand(RunCmd) 84 SingularityCmd.AddCommand(TestCmd) 85 } 86 87 func handleOCI(cmd *cobra.Command, u string) (string, error) { 88 authConf, err := makeDockerCredentials(cmd) 89 if err != nil { 90 sylog.Fatalf("While creating Docker credentials: %v", err) 91 } 92 93 sysCtx := &ocitypes.SystemContext{ 94 OCIInsecureSkipTLSVerify: noHTTPS, 95 DockerInsecureSkipTLSVerify: noHTTPS, 96 DockerAuthConfig: authConf, 97 } 98 99 sum, err := ociclient.ImageSHA(u, sysCtx) 100 if err != nil { 101 return "", fmt.Errorf("failed to get SHA of %v: %v", u, err) 102 } 103 104 name := uri.GetName(u) 105 imgabs := cache.OciTempImage(sum, name) 106 107 if exists, err := cache.OciTempExists(sum, name); err != nil { 108 return "", fmt.Errorf("unable to check if %v exists: %v", imgabs, err) 109 } else if !exists { 110 sylog.Infof("Converting OCI blobs to SIF format") 111 b, err := build.NewBuild(u, imgabs, "sif", "", "", types.Options{TmpDir: tmpDir, NoTest: true, NoHTTPS: noHTTPS, DockerAuthConfig: authConf}) 112 if err != nil { 113 return "", fmt.Errorf("unable to create new build: %v", err) 114 } 115 116 if err := b.Full(); err != nil { 117 return "", fmt.Errorf("unable to build: %v", err) 118 } 119 120 sylog.Infof("Image cached as SIF at %s", imgabs) 121 } 122 123 return imgabs, nil 124 } 125 126 func handleLibrary(u string) (string, error) { 127 libraryImage, err := library.GetImage("https://library.sylabs.io", authToken, u) 128 if err != nil { 129 return "", err 130 } 131 132 imageName := uri.GetName(u) 133 imagePath := cache.LibraryImage(libraryImage.Hash, imageName) 134 135 if exists, err := cache.LibraryImageExists(libraryImage.Hash, imageName); err != nil { 136 return "", fmt.Errorf("unable to check if %v exists: %v", imagePath, err) 137 } else if !exists { 138 sylog.Infof("Downloading library image") 139 if err = library.DownloadImage(imagePath, u, "https://library.sylabs.io", true, authToken); err != nil { 140 return "", fmt.Errorf("unable to Download Image: %v", err) 141 } 142 143 if cacheFileHash, err := library.ImageHash(imagePath); err != nil { 144 return "", fmt.Errorf("Error getting ImageHash: %v", err) 145 } else if cacheFileHash != libraryImage.Hash { 146 return "", fmt.Errorf("Cached File Hash(%s) and Expected Hash(%s) does not match", cacheFileHash, libraryImage.Hash) 147 } 148 } 149 150 return imagePath, nil 151 } 152 153 func handleShub(u string) (string, error) { 154 imageName := uri.GetName(u) 155 imagePath := cache.ShubImage("hash", imageName) 156 157 libexec.PullShubImage(imagePath, u, true, noHTTPS) 158 159 return imagePath, nil 160 } 161 162 func handleNet(u string) (string, error) { 163 refParts := strings.Split(u, "/") 164 imageName := refParts[len(refParts)-1] 165 imagePath := cache.NetImage("hash", imageName) 166 167 exists, err := cache.NetImageExists("hash", imageName) 168 if err != nil { 169 return "", fmt.Errorf("unable to check if %v exists: %v", imagePath, err) 170 } 171 if !exists { 172 sylog.Infof("Downloading network image") 173 libexec.PullNetImage(imagePath, u, true) 174 } else { 175 sylog.Infof("Use image from cache") 176 } 177 178 return imagePath, nil 179 } 180 181 func replaceURIWithImage(cmd *cobra.Command, args []string) { 182 // If args[0] is not transport:ref (ex. instance://...) formatted return, not a URI 183 t, _ := uri.Split(args[0]) 184 if t == "instance" || t == "" { 185 return 186 } 187 188 var image string 189 var err error 190 191 switch t { 192 case uri.Library: 193 sylabsToken(cmd, args) // Fetch Auth Token for library access 194 195 image, err = handleLibrary(args[0]) 196 case uri.Shub: 197 image, err = handleShub(args[0]) 198 case ociclient.IsSupported(t): 199 image, err = handleOCI(cmd, args[0]) 200 case uri.HTTP: 201 image, err = handleNet(args[0]) 202 case uri.HTTPS: 203 image, err = handleNet(args[0]) 204 default: 205 sylog.Fatalf("Unsupported transport type: %s", t) 206 } 207 208 if err != nil { 209 sylog.Fatalf("Unable to handle %s uri: %v", args[0], err) 210 } 211 212 args[0] = image 213 return 214 } 215 216 // ExecCmd represents the exec command 217 var ExecCmd = &cobra.Command{ 218 DisableFlagsInUseLine: true, 219 TraverseChildren: true, 220 Args: cobra.MinimumNArgs(2), 221 PreRun: replaceURIWithImage, 222 Run: func(cmd *cobra.Command, args []string) { 223 a := append([]string{"/.singularity.d/actions/exec"}, args[1:]...) 224 execStarter(cmd, args[0], a, "") 225 }, 226 227 Use: docs.ExecUse, 228 Short: docs.ExecShort, 229 Long: docs.ExecLong, 230 Example: docs.ExecExamples, 231 } 232 233 // ShellCmd represents the shell command 234 var ShellCmd = &cobra.Command{ 235 DisableFlagsInUseLine: true, 236 TraverseChildren: true, 237 Args: cobra.MinimumNArgs(1), 238 PreRun: replaceURIWithImage, 239 Run: func(cmd *cobra.Command, args []string) { 240 a := []string{"/.singularity.d/actions/shell"} 241 execStarter(cmd, args[0], a, "") 242 }, 243 244 Use: docs.ShellUse, 245 Short: docs.ShellShort, 246 Long: docs.ShellLong, 247 Example: docs.ShellExamples, 248 } 249 250 // RunCmd represents the run command 251 var RunCmd = &cobra.Command{ 252 DisableFlagsInUseLine: true, 253 TraverseChildren: true, 254 Args: cobra.MinimumNArgs(1), 255 PreRun: replaceURIWithImage, 256 Run: func(cmd *cobra.Command, args []string) { 257 a := append([]string{"/.singularity.d/actions/run"}, args[1:]...) 258 execStarter(cmd, args[0], a, "") 259 }, 260 261 Use: docs.RunUse, 262 Short: docs.RunShort, 263 Long: docs.RunLong, 264 Example: docs.RunExamples, 265 } 266 267 // TestCmd represents the test command 268 var TestCmd = &cobra.Command{ 269 DisableFlagsInUseLine: true, 270 TraverseChildren: true, 271 Args: cobra.MinimumNArgs(1), 272 PreRun: replaceURIWithImage, 273 Run: func(cmd *cobra.Command, args []string) { 274 a := append([]string{"/.singularity.d/actions/test"}, args[1:]...) 275 execStarter(cmd, args[0], a, "") 276 }, 277 278 Use: docs.RunTestUse, 279 Short: docs.RunTestShort, 280 Long: docs.RunTestLong, 281 Example: docs.RunTestExample, 282 }