about summary refs log tree commit diff
path: root/tools/nixery/manifest/manifest.go
blob: d61514d2f62d5a0be0166daff618a2c370c83afa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// 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:"cmd,omitempty"`
		Env []string `json:"env,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
}