github.com/apptainer/singularity@v3.1.1+incompatible/pkg/build/types/definition.go (about)

     1  // Copyright (c) 2018, 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 types
     7  
     8  import (
     9  	"bytes"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io"
    13  	"strings"
    14  )
    15  
    16  // Definition describes how to build an image.
    17  type Definition struct {
    18  	Header     map[string]string `json:"header"`
    19  	ImageData  `json:"imageData"`
    20  	BuildData  Data              `json:"buildData"`
    21  	CustomData map[string]string `json:"customData"`
    22  	Raw        []byte            `json:"raw"`
    23  }
    24  
    25  // ImageData contains any scripts, metadata, etc... that needs to be
    26  // present in some from in the final built image
    27  type ImageData struct {
    28  	Metadata     []byte            `json:"metadata"`
    29  	Labels       map[string]string `json:"labels"`
    30  	ImageScripts `json:"imageScripts"`
    31  }
    32  
    33  // ImageScripts contains scripts that are used after build time.
    34  type ImageScripts struct {
    35  	Help        string `json:"help"`
    36  	Environment string `json:"environment"`
    37  	Runscript   string `json:"runScript"`
    38  	Test        string `json:"test"`
    39  	Startscript string `json:"startScript"`
    40  }
    41  
    42  // Data contains any scripts, metadata, etc... that the Builder may
    43  // need to know only at build time to build the image
    44  type Data struct {
    45  	Files   []FileTransport `json:"files"`
    46  	Scripts `json:"buildScripts"`
    47  }
    48  
    49  // FileTransport holds source and destination information of files to copy into the container
    50  type FileTransport struct {
    51  	Src string `json:"source"`
    52  	Dst string `json:"destination"`
    53  }
    54  
    55  // Scripts defines scripts that are used at build time.
    56  type Scripts struct {
    57  	Pre   string `json:"pre"`
    58  	Setup string `json:"setup"`
    59  	Post  string `json:"post"`
    60  	Test  string `json:"test"`
    61  }
    62  
    63  // NewDefinitionFromURI crafts a new Definition given a URI
    64  func NewDefinitionFromURI(uri string) (d Definition, err error) {
    65  	var u []string
    66  	if strings.Contains(uri, "://") {
    67  		u = strings.SplitN(uri, "://", 2)
    68  	} else if strings.Contains(uri, ":") {
    69  		u = strings.SplitN(uri, ":", 2)
    70  	} else {
    71  		return d, fmt.Errorf("build URI must start with prefix:// or prefix: ")
    72  	}
    73  
    74  	d = Definition{
    75  		Header: map[string]string{
    76  			"bootstrap": u[0],
    77  			"from":      u[1],
    78  		},
    79  	}
    80  
    81  	var buf bytes.Buffer
    82  	populateRaw(&d, &buf)
    83  	d.Raw = buf.Bytes()
    84  
    85  	return d, nil
    86  }
    87  
    88  // NewDefinitionFromJSON creates a new Definition using the supplied JSON.
    89  func NewDefinitionFromJSON(r io.Reader) (d Definition, err error) {
    90  	decoder := json.NewDecoder(r)
    91  
    92  	for {
    93  		if err = decoder.Decode(&d); err == io.EOF {
    94  			break
    95  		} else if err != nil {
    96  			return
    97  		}
    98  	}
    99  
   100  	// if JSON definition doesn't have a raw data section, add it
   101  	if len(d.Raw) == 0 {
   102  		var buf bytes.Buffer
   103  		populateRaw(&d, &buf)
   104  		d.Raw = buf.Bytes()
   105  	}
   106  
   107  	return d, nil
   108  }
   109  
   110  func writeSectionIfExists(w io.Writer, ident string, s string) {
   111  	if len(s) > 0 {
   112  		w.Write([]byte("%"))
   113  		w.Write([]byte(ident))
   114  		w.Write([]byte("\n"))
   115  		w.Write([]byte(s))
   116  		w.Write([]byte("\n\n"))
   117  	}
   118  }
   119  
   120  func writeFilesIfExists(w io.Writer, f []FileTransport) {
   121  
   122  	if len(f) > 0 {
   123  
   124  		w.Write([]byte("%"))
   125  		w.Write([]byte("files"))
   126  		w.Write([]byte("\n"))
   127  
   128  		for _, ft := range f {
   129  			w.Write([]byte("\t"))
   130  			w.Write([]byte(ft.Src))
   131  			w.Write([]byte("\t"))
   132  			w.Write([]byte(ft.Dst))
   133  			w.Write([]byte("\n"))
   134  		}
   135  		w.Write([]byte("\n"))
   136  	}
   137  }
   138  
   139  func writeLabelsIfExists(w io.Writer, l map[string]string) {
   140  
   141  	if len(l) > 0 {
   142  
   143  		w.Write([]byte("%"))
   144  		w.Write([]byte("labels"))
   145  		w.Write([]byte("\n"))
   146  
   147  		for k, v := range l {
   148  			w.Write([]byte("\t"))
   149  			w.Write([]byte(k))
   150  			w.Write([]byte(" "))
   151  			w.Write([]byte(v))
   152  			w.Write([]byte("\n"))
   153  		}
   154  		w.Write([]byte("\n"))
   155  	}
   156  }
   157  
   158  // populateRaw is a helper func to output a Definition struct
   159  // into a definition file.
   160  func populateRaw(d *Definition, w io.Writer) {
   161  	for k, v := range d.Header {
   162  		w.Write([]byte(k))
   163  		w.Write([]byte(": "))
   164  		w.Write([]byte(v))
   165  		w.Write([]byte("\n"))
   166  	}
   167  	w.Write([]byte("\n"))
   168  
   169  	writeLabelsIfExists(w, d.ImageData.Labels)
   170  	writeFilesIfExists(w, d.BuildData.Files)
   171  
   172  	writeSectionIfExists(w, "help", d.ImageData.Help)
   173  	writeSectionIfExists(w, "environment", d.ImageData.Environment)
   174  	writeSectionIfExists(w, "runscript", d.ImageData.Runscript)
   175  	writeSectionIfExists(w, "test", d.ImageData.Test)
   176  	writeSectionIfExists(w, "startscript", d.ImageData.Startscript)
   177  	writeSectionIfExists(w, "pre", d.BuildData.Pre)
   178  	writeSectionIfExists(w, "setup", d.BuildData.Setup)
   179  	writeSectionIfExists(w, "post", d.BuildData.Post)
   180  }