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 }