-
Notifications
You must be signed in to change notification settings - Fork 459
/
artifacts.go
226 lines (194 loc) · 7.07 KB
/
artifacts.go
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
// This file is part of MinIO Operator
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package controller
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/docker/cli/cli/config/configfile"
"k8s.io/klog/v2"
// Workaround for auth import issues refer https://github.com/minio/operator/issues/283
_ "k8s.io/client-go/plugin/pkg/client/auth"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/tarball"
miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
)
// minioKeychain implements Keychain to pass custom credentials
type minioKeychain struct {
authn.Keychain
Username string
Password string
Auth string
IdentityToken string
RegistryToken string
}
// Resolve implements Keychain.
func (mk *minioKeychain) Resolve(_ authn.Resource) (authn.Authenticator, error) {
return authn.FromConfig(authn.AuthConfig{
Username: mk.Username,
Password: mk.Password,
Auth: mk.Auth,
IdentityToken: mk.IdentityToken,
RegistryToken: mk.RegistryToken,
}), nil
}
// getKeychainForTenant attempts to build a new authn.Keychain from the image pull secret on the Tenant
func (c *Controller) getKeychainForTenant(ctx context.Context, ref name.Reference, tenant *miniov2.Tenant) (authn.Keychain, error) {
// Get the secret
secret, err := c.kubeClientSet.CoreV1().Secrets(tenant.Namespace).Get(ctx, tenant.Spec.ImagePullSecret.Name, metav1.GetOptions{})
if err != nil {
return authn.DefaultKeychain, errors.New("can't retrieve the tenant image pull secret")
}
// if we can't find .dockerconfigjson, error out
dockerConfigJSON, ok := secret.Data[".dockerconfigjson"]
if !ok {
return authn.DefaultKeychain, fmt.Errorf("unable to find `.dockerconfigjson` in image pull secret")
}
var config configfile.ConfigFile
if err = json.Unmarshal(dockerConfigJSON, &config); err != nil {
return authn.DefaultKeychain, fmt.Errorf("Unable to decode docker config secrets %w", err)
}
cfg, ok := config.AuthConfigs[ref.Context().RegistryStr()]
if !ok {
return authn.DefaultKeychain, fmt.Errorf("unable to locate auth config registry context %s", ref.Context().RegistryStr())
}
return &minioKeychain{
Username: cfg.Username,
Password: cfg.Password,
Auth: cfg.Auth,
IdentityToken: cfg.IdentityToken,
RegistryToken: cfg.RegistryToken,
}, nil
}
// Attempts to fetch given image and then extracts and keeps relevant files
// (minio, minio.sha256sum & minio.minisig) at a pre-defined location (/tmp/webhook/v1/update)
func (c *Controller) fetchArtifacts(tenant *miniov2.Tenant) (latest string, err error) {
c.removeArtifacts() // remove before a fresh fetch.
basePath := updatePath
if err = os.MkdirAll(basePath, 1777); err != nil {
return latest, err
}
ref, err := name.ParseReference(tenant.Spec.Image)
if err != nil {
return latest, err
}
var keychain authn.Keychain
keychain = authn.DefaultKeychain
// if the tenant has imagePullSecret use that for pulling the image, but if we fail to extract the secret or we
// can't find the expected registry in the secret we will continue with the default keychain. This is because the
// needed pull secret could be attached to the service-account.
if tenant.Spec.ImagePullSecret.Name != "" {
// Get the secret
keychain, err = c.getKeychainForTenant(context.Background(), ref, tenant)
if err != nil {
klog.Info(err)
}
}
img, err := remote.Image(ref, remote.WithAuthFromKeychain(keychain))
if err != nil {
return latest, err
}
cfg, err := img.ConfigFile()
if err != nil {
return latest, err
}
tag, ok := cfg.Config.Labels["release"]
if !ok {
tag, ok = cfg.Config.Labels["version"]
}
tag = strings.TrimSpace(tag)
if !ok || tag == "" {
return latest, errors.New("missing tag")
}
ls, err := img.Layers()
if err != nil {
return latest, err
}
// Find the file with largest size among all layers.
// This is the tar file with all minio relevant files.
start := 0
if len(ls) >= 2 { // skip the base layer
start = 1
}
maxSizeHash, _ := ls[start].Digest()
maxSize, _ := ls[start].Size()
for i := range ls {
if i < start {
continue
}
s, _ := ls[i].Size()
if s > maxSize {
maxSize, _ = ls[i].Size()
maxSizeHash, _ = ls[i].Digest()
}
}
f, err := os.OpenFile(basePath+"image.tar", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o777)
if err != nil {
return latest, err
}
// Tarball writes a file called image.tar
// This file in turn has each container layer present inside in the form `<layer-hash>.tar.gz`
if err = tarball.Write(ref, img, f); err != nil {
f.Close()
return latest, err
}
if err = f.Close(); err != nil {
return latest, err
}
// Extract the <layer-hash>.tar.gz file that has minio contents from `image.tar`
fileNameToExtract := strings.Split(maxSizeHash.String(), ":")[1] + ".tar.gz"
if err = miniov2.ExtractTar([]string{fileNameToExtract}, basePath, "image.tar"); err != nil {
return latest, err
}
latestAssets := []string{"opt/bin/minio", "opt/bin/minio.sha256sum", "opt/bin/minio.minisig"}
legacyAssets := []string{"usr/bin/minio", "usr/bin/minio.sha256sum", "usr/bin/minio.minisig"}
// Extract the minio update related files (minio, minio.sha256sum and minio.minisig) from `<layer-hash>.tar.gz`
if err = miniov2.ExtractTar(latestAssets, basePath, fileNameToExtract); err != nil {
// attempt legacy if latest failed to extract artifacts
if err = miniov2.ExtractTar(legacyAssets, basePath, fileNameToExtract); err != nil {
return latest, err
}
}
srcBinary := "minio"
srcShaSum := "minio.sha256sum"
srcSig := "minio.minisig"
if _, err = miniov2.ReleaseTagToReleaseTime(tag); err != nil {
return latest, err
}
destBinary := "minio." + tag
destShaSum := "minio." + tag + ".sha256sum"
destSig := "minio." + tag + ".minisig"
filesToRename := map[string]string{srcBinary: destBinary, srcShaSum: destShaSum, srcSig: destSig}
// rename all files to add tag specific values in the name.
// this is because minio updater looks for files in this name format.
for s, d := range filesToRename {
if err = os.Rename(filepath.Join(basePath, s), filepath.Join(basePath, d)); err != nil {
return tag, err
}
}
return tag, nil
}
// Remove all the files created during upload process
func (c *Controller) removeArtifacts() error {
return os.RemoveAll(updatePath)
}