github.com/apptainer/singularity@v3.1.1+incompatible/cmd/internal/cli/pull.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  	"io"
    10  	"os"
    11  	"os/signal"
    12  	"syscall"
    13  
    14  	"github.com/spf13/cobra"
    15  	"github.com/sylabs/singularity/docs"
    16  	"github.com/sylabs/singularity/internal/pkg/client/cache"
    17  	"github.com/sylabs/singularity/internal/pkg/libexec"
    18  	"github.com/sylabs/singularity/internal/pkg/sylog"
    19  	"github.com/sylabs/singularity/internal/pkg/util/uri"
    20  	"github.com/sylabs/singularity/pkg/build/types"
    21  	client "github.com/sylabs/singularity/pkg/client/library"
    22  )
    23  
    24  const (
    25  	// LibraryProtocol holds the sylabs cloud library base URI
    26  	// for more info refer to https://cloud.sylabs.io/library
    27  	LibraryProtocol = "library"
    28  	// ShubProtocol holds singularity hub base URI
    29  	// for more info refer to https://singularity-hub.org/
    30  	ShubProtocol = "shub"
    31  	// HTTPProtocol holds the remote http base URI
    32  	HTTPProtocol = "http"
    33  	// HTTPSProtocol holds the remote https base URI
    34  	HTTPSProtocol = "https"
    35  )
    36  
    37  var (
    38  	// PullLibraryURI holds the base URI to a Sylabs library API instance
    39  	PullLibraryURI string
    40  	// PullImageName holds the name to be given to the pulled image
    41  	PullImageName string
    42  )
    43  
    44  func init() {
    45  	PullCmd.Flags().SetInterspersed(false)
    46  
    47  	PullCmd.Flags().StringVar(&PullLibraryURI, "library", "https://library.sylabs.io", "the library to pull from")
    48  	PullCmd.Flags().SetAnnotation("library", "envkey", []string{"LIBRARY"})
    49  
    50  	PullCmd.Flags().BoolVarP(&force, "force", "F", false, "overwrite an image file if it exists")
    51  	PullCmd.Flags().SetAnnotation("force", "envkey", []string{"FORCE"})
    52  
    53  	PullCmd.Flags().StringVar(&PullImageName, "name", "", "specify a custom image name")
    54  	PullCmd.Flags().Lookup("name").Hidden = true
    55  	PullCmd.Flags().SetAnnotation("name", "envkey", []string{"NAME"})
    56  
    57  	PullCmd.Flags().StringVar(&tmpDir, "tmpdir", "", "specify a temporary directory to use for build")
    58  	PullCmd.Flags().Lookup("tmpdir").Hidden = true
    59  	PullCmd.Flags().SetAnnotation("tmpdir", "envkey", []string{"TMPDIR"})
    60  
    61  	PullCmd.Flags().BoolVar(&noHTTPS, "nohttps", false, "do NOT use HTTPS, for communicating with local docker registry")
    62  	PullCmd.Flags().SetAnnotation("nohttps", "envkey", []string{"NOHTTPS"})
    63  
    64  	PullCmd.Flags().AddFlag(actionFlags.Lookup("docker-username"))
    65  	PullCmd.Flags().AddFlag(actionFlags.Lookup("docker-password"))
    66  	PullCmd.Flags().AddFlag(actionFlags.Lookup("docker-login"))
    67  
    68  	SingularityCmd.AddCommand(PullCmd)
    69  }
    70  
    71  // PullCmd singularity pull
    72  var PullCmd = &cobra.Command{
    73  	DisableFlagsInUseLine: true,
    74  	Args:                  cobra.RangeArgs(1, 2),
    75  	PreRun:                sylabsToken,
    76  	Run:                   pullRun,
    77  	Use:                   docs.PullUse,
    78  	Short:                 docs.PullShort,
    79  	Long:                  docs.PullLong,
    80  	Example:               docs.PullExample,
    81  }
    82  
    83  func pullRun(cmd *cobra.Command, args []string) {
    84  	i := len(args) - 1 // uri is stored in args[len(args)-1]
    85  	transport, ref := uri.Split(args[i])
    86  	if ref == "" {
    87  		sylog.Fatalf("bad uri %s", args[i])
    88  	}
    89  
    90  	var name string
    91  	if PullImageName == "" {
    92  		name = args[0]
    93  		if len(args) == 1 {
    94  			if transport == "" {
    95  				name = uri.GetName("library://" + args[i])
    96  			} else {
    97  				name = uri.GetName(args[i]) // TODO: If not library/shub & no name specified, simply put to cache
    98  			}
    99  		}
   100  	} else {
   101  		name = PullImageName
   102  	}
   103  
   104  	// monitor for OS signals and remove invalid file
   105  	c := make(chan os.Signal)
   106  	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
   107  	go func(fileName string) {
   108  		<-c
   109  		sylog.Debugf("Removing incomplete file because of receiving Termination signal")
   110  		os.Remove(fileName)
   111  		os.Exit(1)
   112  	}(name)
   113  
   114  	switch transport {
   115  	case LibraryProtocol, "":
   116  		if !force {
   117  			if _, err := os.Stat(name); err == nil {
   118  				sylog.Fatalf("image file already exists - will not overwrite")
   119  			}
   120  		}
   121  
   122  		libraryImage, err := client.GetImage(PullLibraryURI, authToken, args[i])
   123  		if err != nil {
   124  			sylog.Fatalf("While getting image info: %v", err)
   125  		}
   126  
   127  		var imageName string
   128  		if transport == "" {
   129  			imageName = uri.GetName("library://" + args[i])
   130  		} else {
   131  			imageName = uri.GetName(args[i])
   132  		}
   133  		imagePath := cache.LibraryImage(libraryImage.Hash, imageName)
   134  		if exists, err := cache.LibraryImageExists(libraryImage.Hash, imageName); err != nil {
   135  			sylog.Fatalf("unable to check if %v exists: %v", imagePath, err)
   136  		} else if !exists {
   137  			sylog.Infof("Downloading library image")
   138  			if err = client.DownloadImage(imagePath, args[i], PullLibraryURI, true, authToken); err != nil {
   139  				sylog.Fatalf("unable to Download Image: %v", err)
   140  			}
   141  
   142  			if cacheFileHash, err := client.ImageHash(imagePath); err != nil {
   143  				sylog.Fatalf("Error getting ImageHash: %v", err)
   144  			} else if cacheFileHash != libraryImage.Hash {
   145  				sylog.Fatalf("Cached File Hash(%s) and Expected Hash(%s) does not match", cacheFileHash, libraryImage.Hash)
   146  			}
   147  		}
   148  
   149  		// Perms are 777 *prior* to umask
   150  		dstFile, err := os.OpenFile(name, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
   151  		if err != nil {
   152  			sylog.Fatalf("%v\n", err)
   153  		}
   154  		defer dstFile.Close()
   155  
   156  		srcFile, err := os.OpenFile(imagePath, os.O_RDONLY, 0444)
   157  		if err != nil {
   158  			sylog.Fatalf("%v\n", err)
   159  		}
   160  		defer srcFile.Close()
   161  
   162  		// Copy SIF from cache
   163  		_, err = io.Copy(dstFile, srcFile)
   164  		if err != nil {
   165  			sylog.Fatalf("%v\n", err)
   166  		}
   167  	case ShubProtocol:
   168  		libexec.PullShubImage(name, args[i], force, noHTTPS)
   169  	case HTTPProtocol, HTTPSProtocol:
   170  		libexec.PullNetImage(name, args[i], force)
   171  	default:
   172  		if !force {
   173  			if _, err := os.Stat(name); err == nil {
   174  				sylog.Fatalf("image file already exists - will not overwrite")
   175  			}
   176  		}
   177  
   178  		authConf, err := makeDockerCredentials(cmd)
   179  		if err != nil {
   180  			sylog.Fatalf("While creating Docker credentials: %v", err)
   181  		}
   182  
   183  		libexec.PullOciImage(name, args[i], types.Options{
   184  			TmpDir:           tmpDir,
   185  			Force:            force,
   186  			NoHTTPS:          noHTTPS,
   187  			DockerAuthConfig: authConf,
   188  		})
   189  	}
   190  }