nixery.dev uses the vnd.docker.container.image.v1 format, which is recognized by the OCI [1] and originally defined by Docker [2]. The config field in this image format, which this commit is about, is even portable between the Docker and OCI formats (the Docker Golang library embeds the OCI definition [3]). The attribute names in what's called ImageConfig in [3] are specified as PascalCase, which effectively means that the names Env and Cmd used by nixery need to be capitalized. The lowercase variant is not causing a lot of issues because most container tooling is written in Golang, which allows case-insensitive matches when deserializing JSON. Languages that parse strictly either miss the configuration values, or fail due to unknown attributes. This commit capitalizes Cmd and Env to accomodate strict parsers. [1]:365fa41/media-types.md (L70)[2]: https://github.com/moby/moby/blob/v20.10.8/image/spec/v1.2.md#image-json-description [3]:365fa41/specs-go/v1/config.go (L24)Change-Id: Ibee597a64d36c008dea83a3b7a0d8e59b8287d0d Signed-off-by: Markus Rudy <webmaster@burgerdev.de> Co-authored-by: Paul Meyer <49727155+katexochen@users.noreply.github.com> Reviewed-on: https://cl.tvl.fyi/c/depot/+/11012 Autosubmit: lukegb <lukegb@tvl.fyi> Reviewed-by: lukegb <lukegb@tvl.fyi> Tested-by: BuildkiteCI
		
			
				
	
	
		
			135 lines
		
	
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			135 lines
		
	
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2022 The TVL Contributors
 | |
| // SPDX-License-Identifier: Apache-2.0
 | |
| 
 | |
| // Package image implements logic for creating the image metadata
 | |
| // (such as the image manifest and configuration).
 | |
| package manifest
 | |
| 
 | |
| import (
 | |
| 	"crypto/sha256"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"sort"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	// manifest constants
 | |
| 	schemaVersion = 2
 | |
| 
 | |
| 	// media types
 | |
| 	ManifestType = "application/vnd.docker.distribution.manifest.v2+json"
 | |
| 	LayerType    = "application/vnd.docker.image.rootfs.diff.tar.gzip"
 | |
| 	configType   = "application/vnd.docker.container.image.v1+json"
 | |
| 
 | |
| 	// image config constants
 | |
| 	os     = "linux"
 | |
| 	fsType = "layers"
 | |
| )
 | |
| 
 | |
| type Entry struct {
 | |
| 	MediaType string `json:"mediaType,omitempty"`
 | |
| 	Size      int64  `json:"size"`
 | |
| 	Digest    string `json:"digest"`
 | |
| 
 | |
| 	// These fields are internal to Nixery and not part of the
 | |
| 	// serialised entry.
 | |
| 	MergeRating uint64 `json:"-"`
 | |
| 	TarHash     string `json:",omitempty"`
 | |
| }
 | |
| 
 | |
| type manifest struct {
 | |
| 	SchemaVersion int     `json:"schemaVersion"`
 | |
| 	MediaType     string  `json:"mediaType"`
 | |
| 	Config        Entry   `json:"config"`
 | |
| 	Layers        []Entry `json:"layers"`
 | |
| }
 | |
| 
 | |
| type imageConfig struct {
 | |
| 	Architecture string `json:"architecture"`
 | |
| 	OS           string `json:"os"`
 | |
| 
 | |
| 	RootFS struct {
 | |
| 		FSType  string   `json:"type"`
 | |
| 		DiffIDs []string `json:"diff_ids"`
 | |
| 	} `json:"rootfs"`
 | |
| 
 | |
| 	Config struct {
 | |
| 		Cmd []string `json:",omitempty"`
 | |
| 		Env []string `json:",omitempty"`
 | |
| 	} `json:"config"`
 | |
| }
 | |
| 
 | |
| // ConfigLayer represents the configuration layer to be included in
 | |
| // the manifest, containing its JSON-serialised content and SHA256
 | |
| // hash.
 | |
| type ConfigLayer struct {
 | |
| 	Config []byte
 | |
| 	SHA256 string
 | |
| }
 | |
| 
 | |
| // imageConfig creates an image configuration with the values set to
 | |
| // the constant defaults.
 | |
| //
 | |
| // Outside of this module the image configuration is treated as an
 | |
| // opaque blob and it is thus returned as an already serialised byte
 | |
| // array and its SHA256-hash.
 | |
| func configLayer(arch string, hashes []string, cmd string) ConfigLayer {
 | |
| 	c := imageConfig{}
 | |
| 	c.Architecture = arch
 | |
| 	c.OS = os
 | |
| 	c.RootFS.FSType = fsType
 | |
| 	c.RootFS.DiffIDs = hashes
 | |
| 	if cmd != "" {
 | |
| 		c.Config.Cmd = []string{cmd}
 | |
| 	}
 | |
| 	c.Config.Env = []string{"SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"}
 | |
| 
 | |
| 	j, _ := json.Marshal(c)
 | |
| 
 | |
| 	return ConfigLayer{
 | |
| 		Config: j,
 | |
| 		SHA256: fmt.Sprintf("%x", sha256.Sum256(j)),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Manifest creates an image manifest from the specified layer entries
 | |
| // and returns its JSON-serialised form as well as the configuration
 | |
| // layer.
 | |
| //
 | |
| // Callers do not need to set the media type for the layer entries.
 | |
| func Manifest(arch string, layers []Entry, cmd string) (json.RawMessage, ConfigLayer) {
 | |
| 	// Sort layers by their merge rating, from highest to lowest.
 | |
| 	// This makes it likely for a contiguous chain of shared image
 | |
| 	// layers to appear at the beginning of a layer.
 | |
| 	//
 | |
| 	// Due to moby/moby#38446 Docker considers the order of layers
 | |
| 	// when deciding which layers to download again.
 | |
| 	sort.Slice(layers, func(i, j int) bool {
 | |
| 		return layers[i].MergeRating > layers[j].MergeRating
 | |
| 	})
 | |
| 
 | |
| 	hashes := make([]string, len(layers))
 | |
| 	for i, l := range layers {
 | |
| 		hashes[i] = l.TarHash
 | |
| 		l.MediaType = LayerType
 | |
| 		l.TarHash = ""
 | |
| 		layers[i] = l
 | |
| 	}
 | |
| 
 | |
| 	c := configLayer(arch, hashes, cmd)
 | |
| 
 | |
| 	m := manifest{
 | |
| 		SchemaVersion: schemaVersion,
 | |
| 		MediaType:     ManifestType,
 | |
| 		Config: Entry{
 | |
| 			MediaType: configType,
 | |
| 			Size:      int64(len(c.Config)),
 | |
| 			Digest:    "sha256:" + c.SHA256,
 | |
| 		},
 | |
| 		Layers: layers,
 | |
| 	}
 | |
| 
 | |
| 	j, _ := json.Marshal(m)
 | |
| 
 | |
| 	return json.RawMessage(j), c
 | |
| }
 |