-
Notifications
You must be signed in to change notification settings - Fork 54
Expand file tree
/
Copy pathfetch.go
More file actions
228 lines (172 loc) · 7.54 KB
/
Copy pathfetch.go
File metadata and controls
228 lines (172 loc) · 7.54 KB
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package artifacts
import (
"archive/tar"
"context"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/google/go-containerregistry/pkg/name"
"github.com/siderolabs/talos/pkg/machinery/imager/quirks"
"go.uber.org/zap"
"github.com/siderolabs/image-factory/internal/artifacts/imagehandler"
"github.com/siderolabs/image-factory/internal/image/verify"
)
// fetchImageByTag contains combined logic of image handling: heading, downloading, verifying signatures, and exporting.
// Uses the default image registry.
func (m *Manager) fetchImageByTag(imageName, tag string, architecture Arch, imageHandler imagehandler.Handler) error {
return m.fetchImageByTagWithRepo(imageName, tag, m.imageRegistry, architecture, imageHandler)
}
// fetchImageByTag contains combined logic of image handling: heading, downloading, verifying signatures, and exporting.
func (m *Manager) fetchImageByTagWithRepo(imageName, tag string, reg name.Registry, architecture Arch, imageHandler imagehandler.Handler) error {
// set a timeout for fetching, but don't bind it to any context, as we want fetch operation to finish
ctx, cancel := context.WithTimeout(context.Background(), FetchTimeout)
defer cancel()
// light check first - if the image exists, and resolve the digest
// it's important to do further checks by digest exactly
repoRef := reg.Repo(imageName).Tag(tag)
m.logger.Debug("heading the image", zap.Stringer("image", repoRef))
descriptor, err := m.pullers[architecture].Head(ctx, repoRef)
if err != nil {
return err
}
digestRef := repoRef.Digest(descriptor.Digest.String())
return m.fetchImageByDigest(digestRef, architecture, imageHandler)
}
// fetchImageByDigest fetches an image by digest, verifies signatures, and exports it to the storage.
func (m *Manager) fetchImageByDigest(digestRef name.Digest, architecture Arch, imageHandler imagehandler.Handler) error {
var err error
// set a timeout for fetching, but don't bind it to any context, as we want fetch operation to finish
ctx, cancel := context.WithTimeout(context.Background(), FetchTimeout)
defer cancel()
logger := m.logger.With(zap.Stringer("image", digestRef))
// verify the image signature, we only accept properly signed images
logger.Debug("verifying image signature")
var nameOptions []name.Option
if digestRef.Scheme() == "http" {
nameOptions = append(nameOptions, name.Insecure)
}
verifyResult, err := verify.VerifySignatures(ctx, digestRef, m.options.ImageVerifyOptions, nameOptions...)
if err != nil {
return fmt.Errorf("failed to verify image signature for %s: %w", digestRef.Name(), err)
}
logger.Info("image signature verified", zap.String("verification_method", verifyResult.Method), zap.Bool("bundle_verified", verifyResult.Verified))
// pull down the image and extract the necessary parts
logger.Info("pulling the image")
desc, err := m.pullers[architecture].Get(ctx, digestRef)
if err != nil {
return fmt.Errorf("error pulling image %s: %w", digestRef, err)
}
img, err := desc.Image()
if err != nil {
return fmt.Errorf("error creating image from descriptor: %w", err)
}
return imageHandler(ctx, logger, img)
}
// fetchImager fetches 'imager' container, and saves to the storage path.
func (m *Manager) fetchImager(tag string) error {
destinationPath := filepath.Join(m.storagePath, tag)
if err := m.fetchImageByTag(m.options.ImagerImage, tag, ArchAmd64, imagehandler.Export(func(logger *zap.Logger, r io.Reader) error {
return untarWithPrefix(logger, r, usrInstallPrefix, destinationPath+tmpSuffix)
})); err != nil {
return err
}
return os.Rename(destinationPath+tmpSuffix, destinationPath)
}
// extractOverlay fetches 'overlay' container, and saves to the storage path.
func (m *Manager) extractOverlay(arch Arch, ref OverlayRef) error {
imageRef := m.imageRegistry.Repo(ref.TaggedReference.RepositoryStr()).Digest(ref.Digest)
destinationPath := filepath.Join(m.storagePath, string(arch)+"-"+ref.Digest+"-overlay")
if err := m.fetchImageByDigest(imageRef, arch, imagehandler.Export(func(logger *zap.Logger, r io.Reader) error {
return untarWithPrefix(logger, r, overlaysPrefix, destinationPath+tmpSuffix)
})); err != nil {
return err
}
return os.Rename(destinationPath+tmpSuffix, destinationPath)
}
// fetchExtensionImage fetches a specified extension image and exports it to the storage as OCI.
func (m *Manager) fetchExtensionImage(arch Arch, ref ExtensionRef, destPath string) error {
imageRef := ref.TaggedReference.Digest(ref.Digest)
if err := m.fetchImageByDigest(imageRef, arch, imagehandler.OCI(destPath+tmpSuffix)); err != nil {
return err
}
return os.Rename(destPath+tmpSuffix, destPath)
}
// fetchOverlayImage fetches a specified overlay image and exports it to the storage as OCI.
func (m *Manager) fetchOverlayImage(arch Arch, ref OverlayRef, destPath string) error {
imageRef := m.imageRegistry.Repo(ref.TaggedReference.RepositoryStr()).Digest(ref.Digest)
if err := m.fetchImageByDigest(imageRef, arch, imagehandler.OCI(destPath+tmpSuffix)); err != nil {
return err
}
return os.Rename(destPath+tmpSuffix, destPath)
}
// InstallerImageName returns an installer image name based on Talos version.
func (m *Manager) InstallerImageName(versionTag string) string {
if quirks.New(versionTag).SupportsUnifiedInstaller() {
return m.options.InstallerBaseImage
}
return m.options.InstallerImage
}
// fetchInstallerImage fetches a Talos installer image and exports it to the storage.
func (m *Manager) fetchInstallerImage(arch Arch, versionTag string, destPath string) error {
if err := m.fetchImageByTag(m.InstallerImageName(versionTag), versionTag, arch, imagehandler.OCI(destPath+tmpSuffix)); err != nil {
return err
}
return os.Rename(destPath+tmpSuffix, destPath)
}
// fetchTalosctlImage fetches a Talosctl image and exports it to the storage.
func (m *Manager) fetchTalosctlImage(versionTag string, destPath string) error {
if err := m.fetchImageByTag(m.options.TalosctlImage, versionTag, ArchAmd64, imagehandler.Export(func(logger *zap.Logger, r io.Reader) error {
return untarWithPrefix(logger, r, "", destPath+tmpSuffix)
})); err != nil {
return err
}
return os.Rename(destPath+tmpSuffix, destPath)
}
const (
usrInstallPrefix = "usr/install/"
overlaysPrefix = ""
)
func untarWithPrefix(logger *zap.Logger, r io.Reader, prefix, destination string) error {
tr := tar.NewReader(r)
size := int64(0)
for {
hdr, err := tr.Next()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return fmt.Errorf("error reading tar header: %w", err)
}
if hdr.Typeflag != tar.TypeReg || !strings.HasPrefix(hdr.Name, prefix) { // skip
_, err = io.Copy(io.Discard, tr)
if err != nil {
return fmt.Errorf("error skipping data: %w", err)
}
continue
}
destPath := filepath.Join(destination, hdr.Name[len(prefix):])
if err = os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {
return fmt.Errorf("error creating directory %q: %w", filepath.Dir(destPath), err)
}
f, err := os.Create(destPath)
if err != nil {
return fmt.Errorf("error creating file %q: %w", destPath, err)
}
_, err = io.Copy(f, tr)
if err != nil {
return fmt.Errorf("error copying data to %q: %w", destPath, err)
}
if err = f.Close(); err != nil {
return fmt.Errorf("error closing %q: %w", destPath, err)
}
size += hdr.Size
}
logger.Info("extracted the image", zap.Int64("size", size), zap.String("destination", destination))
return nil
}