github.com/apptainer/singularity@v3.1.1+incompatible/cmd/internal/cli/inspect_linux.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  	"encoding/json"
    10  	"fmt"
    11  	"log"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  
    16  	"github.com/opencontainers/runtime-tools/generate"
    17  	"github.com/spf13/cobra"
    18  	"github.com/sylabs/singularity/docs"
    19  	"github.com/sylabs/singularity/internal/pkg/buildcfg"
    20  	"github.com/sylabs/singularity/internal/pkg/sylog"
    21  	"github.com/sylabs/singularity/internal/pkg/util/exec"
    22  
    23  	"github.com/sylabs/singularity/internal/pkg/runtime/engines/config"
    24  	"github.com/sylabs/singularity/internal/pkg/runtime/engines/config/oci"
    25  	singularityConfig "github.com/sylabs/singularity/internal/pkg/runtime/engines/singularity/config"
    26  )
    27  
    28  var (
    29  	labels      bool
    30  	deffile     bool
    31  	runscript   bool
    32  	testfile    bool
    33  	environment bool
    34  	helpfile    bool
    35  	jsonfmt     bool
    36  )
    37  
    38  func init() {
    39  	InspectCmd.Flags().SetInterspersed(false)
    40  
    41  	InspectCmd.Flags().StringVar(&AppName, "app", "", "inspect a specific app")
    42  	InspectCmd.Flags().SetAnnotation("app", "envkey", []string{"APP"})
    43  
    44  	InspectCmd.Flags().BoolVarP(&labels, "labels", "l", false, "show the labels associated with the image (default)")
    45  	InspectCmd.Flags().SetAnnotation("labels", "envkey", []string{"LABELS"})
    46  
    47  	InspectCmd.Flags().BoolVarP(&deffile, "deffile", "d", false, "show the Singularity recipe file that was used to generate the image")
    48  	InspectCmd.Flags().SetAnnotation("deffile", "envkey", []string{"DEFFILE"})
    49  
    50  	InspectCmd.Flags().BoolVarP(&runscript, "runscript", "r", false, "show the runscript for the image")
    51  	InspectCmd.Flags().SetAnnotation("runscript", "envkey", []string{"RUNSCRIPT"})
    52  
    53  	InspectCmd.Flags().BoolVarP(&testfile, "test", "t", false, "show the test script for the image")
    54  	InspectCmd.Flags().SetAnnotation("test", "envkey", []string{"TEST"})
    55  
    56  	InspectCmd.Flags().BoolVarP(&environment, "environment", "e", false, "show the environment settings for the image")
    57  	InspectCmd.Flags().SetAnnotation("environment", "envkey", []string{"ENVIRONMENT"})
    58  
    59  	InspectCmd.Flags().BoolVarP(&helpfile, "helpfile", "H", false, "inspect the runscript helpfile, if it exists")
    60  	InspectCmd.Flags().SetAnnotation("helpfile", "envkey", []string{"HELPFILE"})
    61  
    62  	InspectCmd.Flags().BoolVarP(&jsonfmt, "json", "j", false, "print structured json instead of sections")
    63  	InspectCmd.Flags().SetAnnotation("json", "envkey", []string{"JSON"})
    64  
    65  	SingularityCmd.AddCommand(InspectCmd)
    66  }
    67  
    68  func getLabelsFile(appName string) string {
    69  	if appName == "" {
    70  		return " cat /.singularity.d/labels.json;"
    71  	}
    72  
    73  	return fmt.Sprintf(" cat /scif/apps/%s/scif/labels.json;", appName)
    74  }
    75  
    76  func getRunscriptFile(appName string) string {
    77  	if appName == "" {
    78  		return " cat /.singularity.d/runscript;"
    79  	}
    80  
    81  	return fmt.Sprintf("/scif/apps/%s/scif/runscript", appName)
    82  }
    83  
    84  func getTestFile(appName string) string {
    85  	if appName == "" {
    86  		return " cat /.singularity.d/test;"
    87  	}
    88  
    89  	return fmt.Sprintf("/scif/apps/%s/scif/test", appName)
    90  }
    91  
    92  func getEnvFile(appName string) string {
    93  	if appName == "" {
    94  		return " find /.singularity.d/env -name 9*-environment.sh -exec echo -n == \\; -exec basename -z {} \\; -exec echo == \\; -exec cat {} \\; -exec echo \\;;"
    95  	}
    96  
    97  	return fmt.Sprintf(" find /scif/apps/%s/scif/env -name 9*-environment.sh -exec echo -n == \\; -exec basename -z {} \\; -exec echo == \\; -exec cat {} \\; -exec echo \\;;", appName)
    98  }
    99  
   100  func getHelpFile(appName string) string {
   101  	if appName == "" {
   102  		return " cat /.singularity.d/runscript.help;"
   103  	}
   104  
   105  	return fmt.Sprintf("/scif/apps/%s/scif/runscript.help", appName)
   106  }
   107  
   108  // InspectCmd represents the build command
   109  var InspectCmd = &cobra.Command{
   110  	DisableFlagsInUseLine: true,
   111  	Args:                  cobra.ExactArgs(1),
   112  
   113  	Use:     docs.InspectUse,
   114  	Short:   docs.InspectShort,
   115  	Long:    docs.InspectLong,
   116  	Example: docs.InspectExample,
   117  
   118  	Run: func(cmd *cobra.Command, args []string) {
   119  
   120  		// Sanity check
   121  		if _, err := os.Stat(args[0]); err != nil {
   122  			sylog.Fatalf("container not found: %s", err)
   123  		}
   124  
   125  		abspath, err := filepath.Abs(args[0])
   126  		if err != nil {
   127  			sylog.Fatalf("While determining absolute file path: %v", err)
   128  		}
   129  		name := filepath.Base(abspath)
   130  
   131  		attributes := make(map[string]string)
   132  
   133  		a := []string{"/bin/sh", "-c", ""}
   134  		prefix := "@@@start"
   135  		delimiter := "@@@end"
   136  
   137  		if helpfile {
   138  			sylog.Debugf("Inspection of helpfile selected.")
   139  
   140  			// append to a[2] to run commands in container
   141  			a[2] += fmt.Sprintf(" echo '%v\nhelpfile';", prefix)
   142  			a[2] += getHelpFile(AppName)
   143  			a[2] += fmt.Sprintf(" echo '%v';", delimiter)
   144  		}
   145  
   146  		if deffile {
   147  			sylog.Debugf("Inspection of deffile selected.")
   148  
   149  			// append to a[2] to run commands in container
   150  			a[2] += fmt.Sprintf(" echo '%v\ndeffile';", prefix)
   151  			a[2] += " cat .singularity.d/Singularity;" // apps share common definition file
   152  			a[2] += fmt.Sprintf(" echo '%v';", delimiter)
   153  		}
   154  
   155  		if runscript {
   156  			sylog.Debugf("Inspection of runscript selected.")
   157  
   158  			// append to a[2] to run commands in container
   159  			a[2] += fmt.Sprintf(" echo '%v\nrunscript';", prefix)
   160  			a[2] += getRunscriptFile(AppName)
   161  			a[2] += fmt.Sprintf(" echo '%v';", delimiter)
   162  		}
   163  
   164  		if testfile {
   165  			sylog.Debugf("Inspection of test selected.")
   166  
   167  			// append to a[2] to run commands in container
   168  			a[2] += fmt.Sprintf(" echo '%v\ntest';", prefix)
   169  			a[2] += getTestFile(AppName)
   170  			a[2] += fmt.Sprintf(" echo '%v';", delimiter)
   171  		}
   172  
   173  		if environment {
   174  			sylog.Debugf("Inspection of environment selected.")
   175  
   176  			// append to a[2] to run commands in container
   177  			a[2] += fmt.Sprintf(" echo '%v\nenvironment';", prefix)
   178  			a[2] += getEnvFile(AppName)
   179  			a[2] += fmt.Sprintf(" echo '%v';", delimiter)
   180  		}
   181  
   182  		// default to labels if nothing was appended
   183  		if labels || len(a[2]) == 0 {
   184  			sylog.Debugf("Inspection of labels as default.")
   185  
   186  			// append to a[2] to run commands in container
   187  			a[2] += fmt.Sprintf(" echo '%v\nlabels';", prefix)
   188  			a[2] += getLabelsFile(AppName)
   189  			a[2] += fmt.Sprintf(" echo '%v';", delimiter)
   190  		}
   191  
   192  		fileContents, err := getFileContent(abspath, name, a)
   193  		if err != nil {
   194  			sylog.Fatalf("While getting helpfile: %v", err)
   195  		}
   196  
   197  		contentSlice := strings.Split(fileContents, delimiter)
   198  		for _, s := range contentSlice {
   199  			s = strings.TrimSpace(s)
   200  			if strings.HasPrefix(s, prefix) {
   201  				split := strings.SplitN(s, "\n", 3)
   202  				if len(split) == 3 {
   203  					attributes[split[1]] = split[2]
   204  				} else if len(split) == 2 {
   205  					sylog.Warningf("%v metadata was not found.", split[1])
   206  				}
   207  			}
   208  		}
   209  
   210  		// format that data based on --json flag
   211  		if jsonfmt {
   212  			// store this in a struct, then marshal the struct to json
   213  			type result struct {
   214  				Data map[string]string `json:"attributes"`
   215  				T    string            `json:"type"`
   216  			}
   217  
   218  			d := result{
   219  				Data: attributes,
   220  				T:    "container",
   221  			}
   222  
   223  			b, err := json.MarshalIndent(d, "", "\t")
   224  			if err != nil {
   225  				log.Fatal(err)
   226  			}
   227  
   228  			fmt.Println(string(b))
   229  		} else {
   230  			// iterate through sections of struct and print them
   231  			for _, value := range attributes {
   232  				fmt.Println("\n" + value + "\n")
   233  			}
   234  		}
   235  
   236  	},
   237  	TraverseChildren: true,
   238  }
   239  
   240  func getFileContent(abspath, name string, args []string) (string, error) {
   241  	starter := buildcfg.LIBEXECDIR + "/singularity/bin/starter-suid"
   242  	procname := "Singularity inspect"
   243  	Env := []string{sylog.GetEnvVar()}
   244  
   245  	engineConfig := singularityConfig.NewConfig()
   246  	ociConfig := &oci.Config{}
   247  	generator := generate.Generator{Config: &ociConfig.Spec}
   248  	engineConfig.OciConfig = ociConfig
   249  
   250  	generator.SetProcessArgs(args)
   251  	generator.SetProcessCwd("/")
   252  	engineConfig.SetImage(abspath)
   253  
   254  	cfg := &config.Common{
   255  		EngineName:   singularityConfig.Name,
   256  		ContainerID:  name,
   257  		EngineConfig: engineConfig,
   258  	}
   259  
   260  	configData, err := json.Marshal(cfg)
   261  	if err != nil {
   262  		sylog.Fatalf("CLI Failed to marshal CommonEngineConfig: %s\n", err)
   263  	}
   264  
   265  	//record from stdout and store as a string to return as the contents of the file?
   266  
   267  	cmd, err := exec.PipeCommand(starter, []string{procname}, Env, configData)
   268  	if err != nil {
   269  		sylog.Fatalf("%s", err)
   270  	}
   271  
   272  	b, err := cmd.Output()
   273  	if err != nil {
   274  		sylog.Fatalf("%s", err)
   275  	}
   276  
   277  	return string(b), nil
   278  }