github.com/singularityware/singularity@v3.1.1+incompatible/internal/pkg/build/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 build
     7  
     8  import (
     9  	"encoding/json"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"os"
    14  	"os/exec"
    15  	"os/signal"
    16  	"path/filepath"
    17  	"strconv"
    18  	"syscall"
    19  	"time"
    20  
    21  	specs "github.com/opencontainers/runtime-spec/specs-go"
    22  	"github.com/sylabs/singularity/internal/pkg/build/apps"
    23  	"github.com/sylabs/singularity/internal/pkg/build/assemblers"
    24  	"github.com/sylabs/singularity/internal/pkg/build/sources"
    25  	"github.com/sylabs/singularity/internal/pkg/buildcfg"
    26  	"github.com/sylabs/singularity/internal/pkg/runtime/engines/config"
    27  	"github.com/sylabs/singularity/internal/pkg/runtime/engines/config/oci"
    28  	imgbuildConfig "github.com/sylabs/singularity/internal/pkg/runtime/engines/imgbuild/config"
    29  	"github.com/sylabs/singularity/internal/pkg/sylog"
    30  	syexec "github.com/sylabs/singularity/internal/pkg/util/exec"
    31  	"github.com/sylabs/singularity/internal/pkg/util/uri"
    32  	"github.com/sylabs/singularity/pkg/build/types"
    33  	"github.com/sylabs/singularity/pkg/build/types/parser"
    34  	"github.com/sylabs/singularity/pkg/image"
    35  )
    36  
    37  // Build is an abstracted way to look at the entire build process.
    38  // For example calling NewBuild() will return this object.
    39  // From there we can call Full() on this build object, which will:
    40  // 		Call Bundle() to obtain all data needed to execute the specified build locally on the machine
    41  // 		Execute all of a definition using AllSections()
    42  // 		And finally call Assemble() to create our container image
    43  type Build struct {
    44  	// dest is the location for container after build is complete
    45  	dest string
    46  	// format is the format of built container, e.g., SIF, sandbox
    47  	format string
    48  	// c Gets and Packs data needed to build a container into a Bundle from various sources
    49  	c ConveyorPacker
    50  	// a Assembles a container from the information stored in a Bundle into various formats
    51  	a Assembler
    52  	// b is an intermediate structure that encapsulates all information for the container, e.g., metadata, filesystems
    53  	b *types.Bundle
    54  }
    55  
    56  // NewBuild creates a new Build struct from a spec (URI, definition file, etc...)
    57  func NewBuild(spec, dest, format string, libraryURL, authToken string, opts types.Options) (*Build, error) {
    58  	def, err := makeDef(spec, false)
    59  	if err != nil {
    60  		return nil, fmt.Errorf("unable to parse spec %v: %v", spec, err)
    61  	}
    62  
    63  	return newBuild(def, dest, format, libraryURL, authToken, opts)
    64  }
    65  
    66  // NewBuildJSON creates a new build struct from a JSON byte slice
    67  func NewBuildJSON(r io.Reader, dest, format string, libraryURL, authToken string, opts types.Options) (*Build, error) {
    68  	def, err := types.NewDefinitionFromJSON(r)
    69  	if err != nil {
    70  		return nil, fmt.Errorf("unable to parse JSON: %v", err)
    71  	}
    72  
    73  	return newBuild(def, dest, format, libraryURL, authToken, opts)
    74  }
    75  
    76  func newBuild(d types.Definition, dest, format string, libraryURL, authToken string, opts types.Options) (*Build, error) {
    77  	var err error
    78  
    79  	syscall.Umask(0002)
    80  
    81  	// always build a sandbox if updating an existing sandbox
    82  	if opts.Update {
    83  		format = "sandbox"
    84  	}
    85  
    86  	b := &Build{
    87  		format: format,
    88  		dest:   dest,
    89  	}
    90  
    91  	b.b, err = types.NewBundle(opts.TmpDir, "sbuild")
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	b.b.Recipe = d
    97  	b.b.Opts = opts
    98  
    99  	// dont need to get cp if we're skipping bootstrap
   100  	if !opts.Update || opts.Force {
   101  		if c, err := getcp(b.b.Recipe, libraryURL, authToken); err == nil {
   102  			b.c = c
   103  		} else {
   104  			return nil, fmt.Errorf("unable to get conveyorpacker: %s", err)
   105  		}
   106  	}
   107  
   108  	switch format {
   109  	case "sandbox":
   110  		b.a = &assemblers.SandboxAssembler{}
   111  	case "sif":
   112  		b.a = &assemblers.SIFAssembler{}
   113  	default:
   114  		return nil, fmt.Errorf("unrecognized output format %s", format)
   115  	}
   116  
   117  	return b, nil
   118  }
   119  
   120  // cleanUp removes remnants of build from file system unless NoCleanUp is specified
   121  func (b Build) cleanUp() {
   122  	if b.b.Opts.NoCleanUp {
   123  		sylog.Infof("Build performed with no clean up option, build bundle located at: %v", b.b.Path)
   124  		return
   125  	}
   126  	sylog.Debugf("Build bundle cleanup: %v", b.b.Path)
   127  	os.RemoveAll(b.b.Path)
   128  }
   129  
   130  // Full runs a standard build from start to finish
   131  func (b *Build) Full() error {
   132  	sylog.Infof("Starting build...")
   133  
   134  	// monitor build for termination signal and clean up
   135  	c := make(chan os.Signal)
   136  	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
   137  	go func() {
   138  		<-c
   139  		b.cleanUp()
   140  		os.Exit(1)
   141  	}()
   142  	// clean up build normally
   143  	defer b.cleanUp()
   144  
   145  	if err := b.runPreScript(); err != nil {
   146  		return err
   147  	}
   148  
   149  	if b.b.Opts.Update && !b.b.Opts.Force {
   150  		//if updating, extract dest container to bundle
   151  		sylog.Infof("Building into existing container: %s", b.dest)
   152  		p, err := sources.GetLocalPacker(b.dest, b.b)
   153  		if err != nil {
   154  			return err
   155  		}
   156  
   157  		_, err = p.Pack()
   158  		if err != nil {
   159  			return err
   160  		}
   161  	} else {
   162  		//if force, start build from scratch
   163  		if err := b.c.Get(b.b); err != nil {
   164  			return fmt.Errorf("conveyor failed to get: %v", err)
   165  		}
   166  
   167  		_, err := b.c.Pack()
   168  		if err != nil {
   169  			return fmt.Errorf("packer failed to pack: %v", err)
   170  		}
   171  	}
   172  
   173  	// create apps in bundle
   174  	a := apps.New()
   175  	for k, v := range b.b.Recipe.CustomData {
   176  		a.HandleSection(k, v)
   177  	}
   178  
   179  	a.HandleBundle(b.b)
   180  	b.b.Recipe.BuildData.Post += a.HandlePost()
   181  
   182  	if engineRequired(b.b.Recipe) {
   183  		if err := b.runBuildEngine(); err != nil {
   184  			return fmt.Errorf("while running engine: %v", err)
   185  		}
   186  	}
   187  
   188  	sylog.Debugf("Inserting Metadata")
   189  	if err := b.insertMetadata(); err != nil {
   190  		return fmt.Errorf("While inserting metadata to bundle: %v", err)
   191  	}
   192  
   193  	sylog.Debugf("Calling assembler")
   194  	if err := b.Assemble(b.dest); err != nil {
   195  		return err
   196  	}
   197  
   198  	sylog.Infof("Build complete: %s", b.dest)
   199  	return nil
   200  }
   201  
   202  // engineRequired returns true if build definition is requesting to run scripts or copy files
   203  func engineRequired(def types.Definition) bool {
   204  	return def.BuildData.Post != "" || def.BuildData.Setup != "" || def.BuildData.Test != "" || len(def.BuildData.Files) != 0
   205  }
   206  
   207  func (b *Build) copyFiles() error {
   208  
   209  	// iterate through files transfers
   210  	for _, transfer := range b.b.Recipe.BuildData.Files {
   211  		// sanity
   212  		if transfer.Src == "" {
   213  			sylog.Warningf("Attempt to copy file with no name...")
   214  			continue
   215  		}
   216  		// dest = source if not specified
   217  		if transfer.Dst == "" {
   218  			transfer.Dst = transfer.Src
   219  		}
   220  		sylog.Infof("Copying %v to %v", transfer.Src, transfer.Dst)
   221  		// copy each file into bundle rootfs
   222  		transfer.Dst = filepath.Join(b.b.Rootfs(), transfer.Dst)
   223  		copy := exec.Command("/bin/cp", "-fLr", transfer.Src, transfer.Dst)
   224  		if err := copy.Run(); err != nil {
   225  			return fmt.Errorf("While copying %v to %v: %v", transfer.Src, transfer.Dst, err)
   226  		}
   227  	}
   228  
   229  	return nil
   230  }
   231  
   232  func (b *Build) insertMetadata() (err error) {
   233  	// insert help
   234  	err = insertHelpScript(b.b)
   235  	if err != nil {
   236  		return fmt.Errorf("While inserting help script: %v", err)
   237  	}
   238  
   239  	// insert labels
   240  	err = insertLabelsJSON(b.b)
   241  	if err != nil {
   242  		return fmt.Errorf("While inserting labels JSON: %v", err)
   243  	}
   244  
   245  	// insert definition
   246  	err = insertDefinition(b.b)
   247  	if err != nil {
   248  		return fmt.Errorf("While inserting definition: %v", err)
   249  	}
   250  
   251  	// insert environment
   252  	err = insertEnvScript(b.b)
   253  	if err != nil {
   254  		return fmt.Errorf("While inserting environment script: %v", err)
   255  	}
   256  
   257  	// insert startscript
   258  	err = insertStartScript(b.b)
   259  	if err != nil {
   260  		return fmt.Errorf("While inserting startscript: %v", err)
   261  	}
   262  
   263  	// insert runscript
   264  	err = insertRunScript(b.b)
   265  	if err != nil {
   266  		return fmt.Errorf("While inserting runscript: %v", err)
   267  	}
   268  
   269  	// insert test script
   270  	err = insertTestScript(b.b)
   271  	if err != nil {
   272  		return fmt.Errorf("While inserting test script: %v", err)
   273  	}
   274  
   275  	return
   276  }
   277  
   278  func (b *Build) runPreScript() error {
   279  	if b.runPre() && b.b.Recipe.BuildData.Pre != "" {
   280  		if syscall.Getuid() != 0 {
   281  			return fmt.Errorf("Attempted to build with scripts as non-root user")
   282  		}
   283  
   284  		// Run %pre script here
   285  		pre := exec.Command("/bin/sh", "-cex", b.b.Recipe.BuildData.Pre)
   286  		pre.Stdout = os.Stdout
   287  		pre.Stderr = os.Stderr
   288  
   289  		sylog.Infof("Running pre scriptlet\n")
   290  		if err := pre.Start(); err != nil {
   291  			return fmt.Errorf("failed to start %%pre proc: %v", err)
   292  		}
   293  		if err := pre.Wait(); err != nil {
   294  			return fmt.Errorf("pre proc: %v", err)
   295  		}
   296  	}
   297  	return nil
   298  }
   299  
   300  // runBuildEngine creates an imgbuild engine and creates a container out of our bundle in order to execute %post %setup scripts in the bundle
   301  func (b *Build) runBuildEngine() error {
   302  	if syscall.Getuid() != 0 {
   303  		return fmt.Errorf("Attempted to build with scripts as non-root user")
   304  	}
   305  
   306  	sylog.Debugf("Starting build engine")
   307  	env := []string{sylog.GetEnvVar()}
   308  	starter := filepath.Join(buildcfg.LIBEXECDIR, "/singularity/bin/starter")
   309  	progname := []string{"singularity image-build"}
   310  	ociConfig := &oci.Config{}
   311  
   312  	engineConfig := &imgbuildConfig.EngineConfig{
   313  		Bundle:    *b.b,
   314  		OciConfig: ociConfig,
   315  	}
   316  
   317  	// surface build specific environment variables for scripts
   318  	sRootfs := "SINGULARITY_ROOTFS=" + b.b.Rootfs()
   319  	sEnvironment := "SINGULARITY_ENVIRONMENT=" + "/.singularity.d/env/91-environment.sh"
   320  
   321  	ociConfig.Process = &specs.Process{}
   322  	ociConfig.Process.Env = append(os.Environ(), sRootfs, sEnvironment)
   323  
   324  	config := &config.Common{
   325  		EngineName:   imgbuildConfig.Name,
   326  		ContainerID:  "image-build",
   327  		EngineConfig: engineConfig,
   328  	}
   329  
   330  	configData, err := json.Marshal(config)
   331  	if err != nil {
   332  		return fmt.Errorf("failed to marshal config.Common: %s", err)
   333  	}
   334  
   335  	starterCmd, err := syexec.PipeCommand(starter, progname, env, configData)
   336  	if err != nil {
   337  		return fmt.Errorf("failed to create cmd type: %v", err)
   338  	}
   339  
   340  	starterCmd.Stdout = os.Stdout
   341  	starterCmd.Stderr = os.Stderr
   342  
   343  	return starterCmd.Run()
   344  }
   345  
   346  func getcp(def types.Definition, libraryURL, authToken string) (ConveyorPacker, error) {
   347  	switch def.Header["bootstrap"] {
   348  	case "library":
   349  		return &sources.LibraryConveyorPacker{
   350  			LibraryURL: libraryURL,
   351  			AuthToken:  authToken,
   352  		}, nil
   353  	case "shub":
   354  		return &sources.ShubConveyorPacker{}, nil
   355  	case "docker", "docker-archive", "docker-daemon", "oci", "oci-archive":
   356  		return &sources.OCIConveyorPacker{}, nil
   357  	case "busybox":
   358  		return &sources.BusyBoxConveyorPacker{}, nil
   359  	case "debootstrap":
   360  		return &sources.DebootstrapConveyorPacker{}, nil
   361  	case "arch":
   362  		return &sources.ArchConveyorPacker{}, nil
   363  	case "localimage":
   364  		return &sources.LocalConveyorPacker{}, nil
   365  	case "yum":
   366  		return &sources.YumConveyorPacker{}, nil
   367  	case "zypper":
   368  		return &sources.ZypperConveyorPacker{}, nil
   369  	case "scratch":
   370  		return &sources.ScratchConveyorPacker{}, nil
   371  	case "":
   372  		return nil, fmt.Errorf("no bootstrap specification found")
   373  	default:
   374  		return nil, fmt.Errorf("invalid build source %s", def.Header["bootstrap"])
   375  	}
   376  }
   377  
   378  // makeDef gets a definition object from a spec
   379  func makeDef(spec string, remote bool) (types.Definition, error) {
   380  	if ok, err := uri.IsValid(spec); ok && err == nil {
   381  		// URI passed as spec
   382  		return types.NewDefinitionFromURI(spec)
   383  	}
   384  
   385  	// Check if spec is an image/sandbox
   386  	if _, err := image.Init(spec, false); err == nil {
   387  		return types.NewDefinitionFromURI("localimage" + "://" + spec)
   388  	}
   389  
   390  	// default to reading file as definition
   391  	defFile, err := os.Open(spec)
   392  	if err != nil {
   393  		return types.Definition{}, fmt.Errorf("unable to open file %s: %v", spec, err)
   394  	}
   395  	defer defFile.Close()
   396  
   397  	// must be root to build from a definition
   398  	if os.Getuid() != 0 && !remote {
   399  		sylog.Fatalf("You must be the root user to build from a Singularity recipe file")
   400  	}
   401  
   402  	d, err := parser.ParseDefinitionFile(defFile)
   403  	if err != nil {
   404  		return types.Definition{}, fmt.Errorf("While parsing definition: %s: %v", spec, err)
   405  	}
   406  
   407  	return d, nil
   408  }
   409  
   410  // runPre determines if %pre section was specified to be run from the CLI
   411  func (b Build) runPre() bool {
   412  	for _, section := range b.b.Opts.Sections {
   413  		if section == "none" {
   414  			return false
   415  		}
   416  		if section == "all" || section == "pre" {
   417  			return true
   418  		}
   419  	}
   420  	return false
   421  }
   422  
   423  // MakeDef gets a definition object from a spec
   424  func MakeDef(spec string, remote bool) (types.Definition, error) {
   425  	return makeDef(spec, remote)
   426  }
   427  
   428  // Assemble assembles the bundle to the specified path
   429  func (b *Build) Assemble(path string) error {
   430  	return b.a.Assemble(b.b, path)
   431  }
   432  
   433  func insertEnvScript(b *types.Bundle) error {
   434  	if b.RunSection("environment") && b.Recipe.ImageData.Environment != "" {
   435  		sylog.Infof("Adding environment to container")
   436  		envScriptPath := filepath.Join(b.Rootfs(), "/.singularity.d/env/90-environment.sh")
   437  		_, err := os.Stat(envScriptPath)
   438  		if os.IsNotExist(err) {
   439  			err := ioutil.WriteFile(envScriptPath, []byte("#!/bin/sh\n\n"+b.Recipe.ImageData.Environment+"\n"), 0755)
   440  			if err != nil {
   441  				return err
   442  			}
   443  		} else {
   444  			// append to script if it already exists
   445  			f, err := os.OpenFile(envScriptPath, os.O_APPEND|os.O_WRONLY, 0755)
   446  			if err != nil {
   447  				return err
   448  			}
   449  			defer f.Close()
   450  
   451  			_, err = f.WriteString("\n" + b.Recipe.ImageData.Environment + "\n")
   452  			if err != nil {
   453  				return err
   454  			}
   455  		}
   456  	}
   457  	return nil
   458  }
   459  
   460  func insertRunScript(b *types.Bundle) error {
   461  	if b.RunSection("runscript") && b.Recipe.ImageData.Runscript != "" {
   462  		sylog.Infof("Adding runscript")
   463  		err := ioutil.WriteFile(filepath.Join(b.Rootfs(), "/.singularity.d/runscript"), []byte("#!/bin/sh\n\n"+b.Recipe.ImageData.Runscript+"\n"), 0755)
   464  		if err != nil {
   465  			return err
   466  		}
   467  	}
   468  	return nil
   469  }
   470  
   471  func insertStartScript(b *types.Bundle) error {
   472  	if b.RunSection("startscript") && b.Recipe.ImageData.Startscript != "" {
   473  		sylog.Infof("Adding startscript")
   474  		err := ioutil.WriteFile(filepath.Join(b.Rootfs(), "/.singularity.d/startscript"), []byte("#!/bin/sh\n\n"+b.Recipe.ImageData.Startscript+"\n"), 0755)
   475  		if err != nil {
   476  			return err
   477  		}
   478  	}
   479  	return nil
   480  }
   481  
   482  func insertTestScript(b *types.Bundle) error {
   483  	if b.RunSection("test") && b.Recipe.ImageData.Test != "" {
   484  		sylog.Infof("Adding testscript")
   485  		err := ioutil.WriteFile(filepath.Join(b.Rootfs(), "/.singularity.d/test"), []byte("#!/bin/sh\n\n"+b.Recipe.ImageData.Test+"\n"), 0755)
   486  		if err != nil {
   487  			return err
   488  		}
   489  	}
   490  	return nil
   491  }
   492  
   493  func insertHelpScript(b *types.Bundle) error {
   494  	if b.RunSection("help") && b.Recipe.ImageData.Help != "" {
   495  		_, err := os.Stat(filepath.Join(b.Rootfs(), "/.singularity.d/runscript.help"))
   496  		if err != nil || b.Opts.Force {
   497  			sylog.Infof("Adding help info")
   498  			err := ioutil.WriteFile(filepath.Join(b.Rootfs(), "/.singularity.d/runscript.help"), []byte(b.Recipe.ImageData.Help+"\n"), 0644)
   499  			if err != nil {
   500  				return err
   501  			}
   502  		} else {
   503  			sylog.Warningf("Help message already exists and force option is false, not overwriting")
   504  		}
   505  	}
   506  	return nil
   507  }
   508  
   509  func insertDefinition(b *types.Bundle) error {
   510  
   511  	// if update, check for existing definition and move it to bootstrap history
   512  	if b.Opts.Update {
   513  		if _, err := os.Stat(filepath.Join(b.Rootfs(), "/.singularity.d/Singularity")); err == nil {
   514  			// make bootstrap_history directory if it doesnt exist
   515  			if _, err := os.Stat(filepath.Join(b.Rootfs(), "/.singularity.d/bootstrap_history")); err != nil {
   516  				err = os.Mkdir(filepath.Join(b.Rootfs(), "/.singularity.d/bootstrap_history"), 0755)
   517  				if err != nil {
   518  					return err
   519  				}
   520  			}
   521  
   522  			// look at number of files in bootstrap_history to give correct file name
   523  			files, err := ioutil.ReadDir(filepath.Join(b.Rootfs(), "/.singularity.d/bootstrap_history"))
   524  
   525  			// name is "Singularity" concatenated with an index based on number of other files in bootstrap_history
   526  			len := strconv.Itoa(len(files))
   527  
   528  			histName := "Singularity" + len
   529  
   530  			// move old definition into bootstrap_history
   531  			err = os.Rename(filepath.Join(b.Rootfs(), "/.singularity.d/Singularity"), filepath.Join(b.Rootfs(), "/.singularity.d/bootstrap_history", histName))
   532  			if err != nil {
   533  				return err
   534  			}
   535  		}
   536  
   537  	}
   538  
   539  	err := ioutil.WriteFile(filepath.Join(b.Rootfs(), "/.singularity.d/Singularity"), b.Recipe.Raw, 0644)
   540  	if err != nil {
   541  		return err
   542  	}
   543  
   544  	return nil
   545  }
   546  
   547  func insertLabelsJSON(b *types.Bundle) (err error) {
   548  	var text []byte
   549  	labels := make(map[string]string)
   550  
   551  	if err = getExistingLabels(labels, b); err != nil {
   552  		return err
   553  	}
   554  
   555  	if err = addBuildLabels(labels, b); err != nil {
   556  		return err
   557  	}
   558  
   559  	if b.RunSection("labels") && len(b.Recipe.ImageData.Labels) > 0 {
   560  		sylog.Infof("Adding labels")
   561  
   562  		// add new labels to new map and check for collisions
   563  		for key, value := range b.Recipe.ImageData.Labels {
   564  			// check if label already exists
   565  			if _, ok := labels[key]; ok {
   566  				// overwrite collision if it exists and force flag is set
   567  				if b.Opts.Force {
   568  					labels[key] = value
   569  				} else {
   570  					sylog.Warningf("Label: %s already exists and force option is false, not overwriting", key)
   571  				}
   572  			} else {
   573  				// set if it doesnt
   574  				labels[key] = value
   575  			}
   576  		}
   577  	}
   578  
   579  	// make new map into json
   580  	text, err = json.MarshalIndent(labels, "", "\t")
   581  	if err != nil {
   582  		return err
   583  	}
   584  
   585  	err = ioutil.WriteFile(filepath.Join(b.Rootfs(), "/.singularity.d/labels.json"), []byte(text), 0644)
   586  	return err
   587  }
   588  
   589  func getExistingLabels(labels map[string]string, b *types.Bundle) error {
   590  	// check for existing labels in bundle
   591  	if _, err := os.Stat(filepath.Join(b.Rootfs(), "/.singularity.d/labels.json")); err == nil {
   592  
   593  		jsonFile, err := os.Open(filepath.Join(b.Rootfs(), "/.singularity.d/labels.json"))
   594  		if err != nil {
   595  			return err
   596  		}
   597  		defer jsonFile.Close()
   598  
   599  		jsonBytes, err := ioutil.ReadAll(jsonFile)
   600  		if err != nil {
   601  			return err
   602  		}
   603  
   604  		err = json.Unmarshal(jsonBytes, &labels)
   605  		if err != nil {
   606  			return err
   607  		}
   608  	}
   609  	return nil
   610  }
   611  
   612  func addBuildLabels(labels map[string]string, b *types.Bundle) error {
   613  	// schema version
   614  	labels["org.label-schema.schema-version"] = "1.0"
   615  
   616  	// build date and time, lots of time formatting
   617  	currentTime := time.Now()
   618  	year, month, day := currentTime.Date()
   619  	date := strconv.Itoa(day) + `_` + month.String() + `_` + strconv.Itoa(year)
   620  	hour, min, sec := currentTime.Clock()
   621  	time := strconv.Itoa(hour) + `:` + strconv.Itoa(min) + `:` + strconv.Itoa(sec)
   622  	zone, _ := currentTime.Zone()
   623  	timeString := currentTime.Weekday().String() + `_` + date + `_` + time + `_` + zone
   624  	labels["org.label-schema.build-date"] = timeString
   625  
   626  	// singularity version
   627  	labels["org.label-schema.usage.singularity.version"] = buildcfg.PACKAGE_VERSION
   628  
   629  	// help info if help exists in the definition and is run in the build
   630  	if b.RunSection("help") && b.Recipe.ImageData.Help != "" {
   631  		labels["org.label-schema.usage"] = "/.singularity.d/runscript.help"
   632  		labels["org.label-schema.usage.singularity.runscript.help"] = "/.singularity.d/runscript.help"
   633  	}
   634  
   635  	// bootstrap header info, only if this build actually bootstrapped
   636  	if !b.Opts.Update || b.Opts.Force {
   637  		for key, value := range b.Recipe.Header {
   638  			labels["org.label-schema.usage.singularity.deffile."+key] = value
   639  		}
   640  	}
   641  
   642  	return nil
   643  }