github.com/apptainer/singularity@v3.1.1+incompatible/cmd/internal/cli/build.go (about) 1 // Copyright (c) 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 "bufio" 10 "fmt" 11 "os" 12 "strings" 13 14 ocitypes "github.com/containers/image/types" 15 "github.com/spf13/cobra" 16 "github.com/spf13/pflag" 17 "github.com/sylabs/singularity/docs" 18 "github.com/sylabs/singularity/internal/pkg/sylog" 19 "github.com/sylabs/singularity/pkg/build/types" 20 "github.com/sylabs/singularity/pkg/build/types/parser" 21 "github.com/sylabs/singularity/pkg/sypgp" 22 ) 23 24 var ( 25 remote bool 26 builderURL string 27 detached bool 28 libraryURL string 29 isJSON bool 30 sandbox bool 31 writable bool 32 force bool 33 update bool 34 noTest bool 35 sections []string 36 noHTTPS bool 37 tmpDir string 38 dockerUsername string 39 dockerPassword string 40 dockerLogin bool 41 noCleanUp bool 42 ) 43 44 var buildflags = pflag.NewFlagSet("BuildFlags", pflag.ExitOnError) 45 46 func init() { 47 BuildCmd.Flags().SetInterspersed(false) 48 49 BuildCmd.Flags().BoolVarP(&sandbox, "sandbox", "s", false, "build image as sandbox format (chroot directory structure)") 50 BuildCmd.Flags().SetAnnotation("sandbox", "envkey", []string{"SANDBOX"}) 51 52 BuildCmd.Flags().StringSliceVar(§ions, "section", []string{"all"}, "only run specific section(s) of deffile (setup, post, files, environment, test, labels, none)") 53 BuildCmd.Flags().SetAnnotation("section", "envkey", []string{"SECTION"}) 54 55 BuildCmd.Flags().BoolVar(&isJSON, "json", false, "interpret build definition as JSON") 56 BuildCmd.Flags().SetAnnotation("json", "envkey", []string{"JSON"}) 57 58 BuildCmd.Flags().BoolVarP(&force, "force", "F", false, "delete and overwrite an image if it currently exists") 59 BuildCmd.Flags().SetAnnotation("force", "envkey", []string{"FORCE"}) 60 61 BuildCmd.Flags().BoolVarP(&update, "update", "u", false, "run definition over existing container (skips header)") 62 BuildCmd.Flags().SetAnnotation("update", "envkey", []string{"UPDATE"}) 63 64 BuildCmd.Flags().BoolVarP(&noTest, "notest", "T", false, "build without running tests in %test section") 65 BuildCmd.Flags().SetAnnotation("notest", "envkey", []string{"NOTEST"}) 66 67 BuildCmd.Flags().BoolVarP(&remote, "remote", "r", false, "build image remotely (does not require root)") 68 BuildCmd.Flags().SetAnnotation("remote", "envkey", []string{"REMOTE"}) 69 70 BuildCmd.Flags().BoolVarP(&detached, "detached", "d", false, "submit build job and print build ID (no real-time logs and requires --remote)") 71 BuildCmd.Flags().SetAnnotation("detached", "envkey", []string{"DETACHED"}) 72 73 BuildCmd.Flags().StringVar(&builderURL, "builder", "https://build.sylabs.io", "remote Build Service URL, setting this implies --remote") 74 BuildCmd.Flags().SetAnnotation("builder", "envkey", []string{"BUILDER"}) 75 76 BuildCmd.Flags().StringVar(&libraryURL, "library", "https://library.sylabs.io", "container Library URL") 77 BuildCmd.Flags().SetAnnotation("library", "envkey", []string{"LIBRARY"}) 78 79 BuildCmd.Flags().StringVar(&tmpDir, "tmpdir", "", "specify a temporary directory to use for build") 80 BuildCmd.Flags().SetAnnotation("tmpdir", "envkey", []string{"TMPDIR"}) 81 82 BuildCmd.Flags().BoolVar(&noHTTPS, "nohttps", false, "do NOT use HTTPS, for communicating with local docker registry") 83 BuildCmd.Flags().SetAnnotation("nohttps", "envkey", []string{"NOHTTPS"}) 84 85 BuildCmd.Flags().BoolVar(&noCleanUp, "no-cleanup", false, "do NOT clean up bundle after failed build, can be helpul for debugging") 86 BuildCmd.Flags().SetAnnotation("no-cleanup", "envkey", []string{"NO_CLEANUP"}) 87 88 BuildCmd.Flags().AddFlag(actionFlags.Lookup("docker-username")) 89 BuildCmd.Flags().AddFlag(actionFlags.Lookup("docker-password")) 90 BuildCmd.Flags().AddFlag(actionFlags.Lookup("docker-login")) 91 92 SingularityCmd.AddCommand(BuildCmd) 93 } 94 95 // BuildCmd represents the build command 96 var BuildCmd = &cobra.Command{ 97 DisableFlagsInUseLine: true, 98 Args: cobra.ExactArgs(2), 99 100 Use: docs.BuildUse, 101 Short: docs.BuildShort, 102 Long: docs.BuildLong, 103 Example: docs.BuildExample, 104 PreRun: preRun, 105 Run: run, 106 TraverseChildren: true, 107 } 108 109 func preRun(cmd *cobra.Command, args []string) { 110 // Always perform remote build when builder flag is set 111 if cmd.Flags().Lookup("builder").Changed { 112 cmd.Flags().Lookup("remote").Value.Set("true") 113 } 114 115 sylabsToken(cmd, args) 116 } 117 118 // checkTargetCollision makes sure output target doesn't exist, or is ok to overwrite 119 func checkBuildTarget(path string, update bool) bool { 120 if f, err := os.Stat(path); err == nil { 121 if update && !f.IsDir() { 122 sylog.Fatalf("Only sandbox updating is supported.") 123 } 124 if !update && !force { 125 reader := bufio.NewReader(os.Stdin) 126 fmt.Print("Build target already exists. Do you want to overwrite? [N/y] ") 127 input, err := reader.ReadString('\n') 128 if err != nil { 129 sylog.Fatalf("Error parsing input: %s", err) 130 } 131 if val := strings.Compare(strings.ToLower(input), "y\n"); val == 0 { 132 force = true 133 } else { 134 sylog.Errorf("Stopping build.") 135 return false 136 } 137 } 138 } 139 return true 140 } 141 142 func checkSections() error { 143 var all, none bool 144 for _, section := range sections { 145 if section == "none" { 146 none = true 147 } 148 if section == "all" { 149 all = true 150 } 151 } 152 153 if all && len(sections) > 1 { 154 return fmt.Errorf("Section specification error: Cannot have all and any other option") 155 } 156 if none && len(sections) > 1 { 157 return fmt.Errorf("Section specification error: Cannot have none and any other option") 158 } 159 160 return nil 161 } 162 163 func definitionFromSpec(spec string) (def types.Definition, err error) { 164 165 // Try spec as URI first 166 def, err = types.NewDefinitionFromURI(spec) 167 if err == nil { 168 return 169 } 170 171 // Try spec as local file 172 var isValid bool 173 isValid, err = parser.IsValidDefinition(spec) 174 if err != nil { 175 return 176 } 177 178 if isValid { 179 sylog.Debugf("Found valid definition: %s\n", spec) 180 // File exists and contains valid definition 181 var defFile *os.File 182 defFile, err = os.Open(spec) 183 if err != nil { 184 return 185 } 186 187 defer defFile.Close() 188 def, err = parser.ParseDefinitionFile(defFile) 189 190 return 191 } 192 193 // File exists and does NOT contain a valid definition 194 // local image or sandbox 195 def = types.Definition{ 196 Header: map[string]string{ 197 "bootstrap": "localimage", 198 "from": spec, 199 }, 200 } 201 202 return 203 } 204 205 func makeDockerCredentials(cmd *cobra.Command) (authConf *ocitypes.DockerAuthConfig, err error) { 206 usernameFlag := cmd.Flags().Lookup("docker-username") 207 passwordFlag := cmd.Flags().Lookup("docker-password") 208 209 if dockerLogin { 210 if !usernameFlag.Changed { 211 dockerUsername, err = sypgp.AskQuestion("Enter Docker Username: ") 212 if err != nil { 213 return 214 } 215 usernameFlag.Value.Set(dockerUsername) 216 usernameFlag.Changed = true 217 } 218 219 dockerPassword, err = sypgp.AskQuestionNoEcho("Enter Docker Password: ") 220 if err != nil { 221 return 222 } 223 passwordFlag.Value.Set(dockerPassword) 224 passwordFlag.Changed = true 225 } 226 227 if usernameFlag.Changed && passwordFlag.Changed { 228 authConf = &ocitypes.DockerAuthConfig{ 229 Username: dockerUsername, 230 Password: dockerPassword, 231 } 232 } 233 234 return 235 }