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(&sections, "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  }