Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/install/installer/cmd/mirror_list.go
2496 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package cmd
6
7
import (
8
"fmt"
9
"os"
10
"regexp"
11
"sort"
12
"strings"
13
14
"github.com/distribution/reference"
15
"github.com/gitpod-io/gitpod/installer/pkg/common"
16
configv1 "github.com/gitpod-io/gitpod/installer/pkg/config/v1"
17
"github.com/spf13/cobra"
18
"k8s.io/utils/pointer"
19
)
20
21
type mirrorListRepo struct {
22
Original string `json:"original"`
23
Target string `json:"target"`
24
}
25
26
var mirrorListOpts struct {
27
ConfigFN string
28
ExcludeThirdParty bool
29
Repository string
30
Domain string
31
}
32
33
// mirrorListCmd represents the mirror list command
34
var mirrorListCmd = &cobra.Command{
35
Use: "list",
36
Short: "Renders a list of images used so they can be mirrored to a third-party registry",
37
Long: `Renders a list of images used so they can be mirrored to a third-party registry
38
39
A config file is required which can be generated with the init command.
40
The "repository" field must be set to your container repository server
41
address and this value will be used to generate the mirrored image names
42
and tags.
43
44
The output can then be used to iterate over each image. A script can
45
be written to pull from the "original" path and then tag and push the
46
image to the "target" repo`,
47
Example: `
48
gitpod-installer mirror list --config config.yaml > mirror.json
49
50
# Pull original and push to target
51
for row in $(gitpod-installer mirror list --config ./config.yaml | jq -c '.[]'); do
52
original=$(echo $row | jq -r '.original')
53
target=$(echo $row | jq -r '.target')
54
docker pull $original
55
docker tag $original $target
56
docker push $target
57
done`,
58
RunE: func(cmd *cobra.Command, args []string) error {
59
if mirrorListOpts.ConfigFN == "" {
60
return fmt.Errorf("config is a required flag")
61
}
62
63
_, cfgVersion, cfg, err := loadConfig(mirrorListOpts.ConfigFN)
64
if err != nil {
65
return err
66
}
67
68
if mirrorListOpts.Repository != "" {
69
cfg.Repository = mirrorListOpts.Repository
70
}
71
72
if mirrorListOpts.Domain != "" {
73
cfg.Domain = mirrorListOpts.Domain
74
}
75
76
images, err := generateMirrorList(cfgVersion, cfg)
77
if err != nil {
78
return err
79
}
80
81
fc, err := common.ToJSONString(images)
82
if err != nil {
83
return err
84
}
85
86
fmt.Println(string(fc))
87
88
return nil
89
},
90
}
91
92
func init() {
93
mirrorCmd.AddCommand(mirrorListCmd)
94
95
mirrorListCmd.Flags().BoolVar(&mirrorListOpts.ExcludeThirdParty, "exclude-third-party", false, "exclude non-Gitpod images")
96
mirrorListCmd.Flags().StringVarP(&mirrorListOpts.ConfigFN, "config", "c", os.Getenv("GITPOD_INSTALLER_CONFIG"), "path to the config file")
97
mirrorListCmd.Flags().StringVar(&mirrorListOpts.Repository, "repository", "", "overwrite the registry in the config")
98
mirrorListCmd.Flags().StringVar(&mirrorListOpts.Domain, "domain", "", "overwrite the domain in the config")
99
}
100
101
func renderAllKubernetesObject(cfgVersion string, cfg *configv1.Config) ([]string, error) {
102
fns := []func() ([]string, error){
103
func() ([]string, error) {
104
// Render for in-cluster dependencies
105
return renderKubernetesObjects(cfgVersion, cfg)
106
},
107
func() ([]string, error) {
108
// Render for external depedencies - AWS
109
cfg.Database = configv1.Database{
110
InCluster: pointer.Bool(false),
111
External: &configv1.DatabaseExternal{
112
Certificate: configv1.ObjectRef{
113
Kind: configv1.ObjectRefSecret,
114
Name: "value",
115
},
116
},
117
}
118
cfg.ContainerRegistry = configv1.ContainerRegistry{
119
InCluster: pointer.Bool(false),
120
External: &configv1.ContainerRegistryExternal{
121
URL: "some-url",
122
Certificate: &configv1.ObjectRef{
123
Kind: configv1.ObjectRefSecret,
124
Name: "value",
125
},
126
},
127
S3Storage: &configv1.S3Storage{
128
Bucket: "some-bucket",
129
Region: "some-region",
130
Endpoint: "some-url",
131
Certificate: &configv1.ObjectRef{
132
Kind: configv1.ObjectRefSecret,
133
Name: "value",
134
},
135
},
136
}
137
cfg.ObjectStorage = configv1.ObjectStorage{
138
InCluster: pointer.Bool(false),
139
S3: &configv1.ObjectStorageS3{
140
Endpoint: "endpoint",
141
BucketName: "some-bucket",
142
Credentials: &configv1.ObjectRef{
143
Kind: configv1.ObjectRefSecret,
144
Name: "value",
145
},
146
},
147
}
148
return renderKubernetesObjects(cfgVersion, cfg)
149
},
150
func() ([]string, error) {
151
// Render for external depedencies - GCP
152
cfg.Database = configv1.Database{
153
InCluster: pointer.Bool(false),
154
CloudSQL: &configv1.DatabaseCloudSQL{
155
Instance: "value",
156
ServiceAccount: configv1.ObjectRef{
157
Kind: configv1.ObjectRefSecret,
158
Name: "value",
159
},
160
},
161
}
162
cfg.ObjectStorage = configv1.ObjectStorage{
163
InCluster: pointer.Bool(false),
164
CloudStorage: &configv1.ObjectStorageCloudStorage{
165
Project: "project",
166
ServiceAccount: configv1.ObjectRef{
167
Kind: configv1.ObjectRefSecret,
168
Name: "value",
169
},
170
},
171
}
172
173
return renderKubernetesObjects(cfgVersion, cfg)
174
},
175
func() ([]string, error) {
176
// Render for ShiftFS
177
cfg.Workspace.Runtime.FSShiftMethod = configv1.FSShiftShiftFS
178
179
return renderKubernetesObjects(cfgVersion, cfg)
180
},
181
}
182
183
var k8s []string
184
for _, fn := range fns {
185
data, err := fn()
186
if err != nil {
187
return nil, err
188
}
189
190
k8s = append(k8s, data...)
191
}
192
193
return k8s, nil
194
}
195
196
func generateMirrorList(cfgVersion string, cfg *configv1.Config) ([]mirrorListRepo, error) {
197
// Throw error if set to the default Gitpod repository
198
if cfg.Repository == common.GitpodContainerRegistry {
199
return nil, fmt.Errorf("cannot mirror images to repository %s", common.GitpodContainerRegistry)
200
}
201
202
// Get the target repository from the config
203
targetRepo := strings.TrimRight(cfg.Repository, "/")
204
205
// Use the default Gitpod registry to pull from
206
cfg.Repository = common.GitpodContainerRegistry
207
208
k8s, err := renderAllKubernetesObject(cfgVersion, cfg)
209
if err != nil {
210
return nil, err
211
}
212
213
// Map of images used for deduping
214
allImages := make(map[string]bool)
215
216
rawImages := make([]string, 0)
217
for _, item := range k8s {
218
rawImages = append(rawImages, getPodImages(item)...)
219
rawImages = append(rawImages, getGenericImages(item)...)
220
}
221
222
images := make([]mirrorListRepo, 0)
223
for _, img := range rawImages {
224
// Ignore if the image equals the container registry
225
if img == common.GitpodContainerRegistry {
226
continue
227
}
228
// Ignore empty image
229
if img == "" {
230
continue
231
}
232
// Dedupe
233
if _, ok := allImages[img]; ok {
234
continue
235
}
236
allImages[img] = true
237
238
// Convert target
239
target := img
240
if strings.Contains(img, cfg.Repository) {
241
// This is the Gitpod registry
242
target = strings.Replace(target, cfg.Repository, targetRepo, 1)
243
} else if !mirrorListOpts.ExcludeThirdParty {
244
// Amend third-party images - remove the first part
245
thirdPartyImg := strings.Join(strings.Split(img, "/")[1:], "/")
246
target = fmt.Sprintf("%s/%s", targetRepo, thirdPartyImg)
247
} else {
248
// Excluding third-party images - just skip this one
249
continue
250
}
251
252
images = append(images, mirrorListRepo{
253
Original: img,
254
Target: target,
255
})
256
}
257
258
// Sort it by the Original
259
sort.Slice(images, func(i, j int) bool {
260
scoreI := images[i].Original
261
scoreJ := images[j].Original
262
263
return scoreI < scoreJ
264
})
265
266
return images, nil
267
}
268
269
// getGenericImages this is a bit brute force - anything starting "docker.io" or with Gitpod repo is found
270
// this will be in ConfigMaps and could be anything, so will need cleaning up
271
func getGenericImages(k8sObj string) []string {
272
var images []string
273
274
// Search for anything that matches docker.io or the Gitpod repo - docker.io needed for gitpod/workspace-full
275
re := regexp.MustCompile(fmt.Sprintf("%s(.*)|%s(.*)", "docker.io", common.GitpodContainerRegistry))
276
img := re.FindAllString(k8sObj, -1)
277
278
if len(img) > 0 {
279
for _, i := range img {
280
// Remove whitespace
281
i = strings.TrimSpace(i)
282
// Remove end commas
283
i = strings.TrimRight(i, ",")
284
// Remove wrapping quotes
285
i = strings.Trim(i, "\"")
286
// Validate the image - assumes images are already fully qualified names
287
_, err := reference.ParseNamed(i)
288
if err != nil {
289
// Invalid - ignore
290
continue
291
}
292
293
images = append(images, i)
294
}
295
}
296
297
return images
298
}
299
300
// getPodImages these are images that are found in the "image:" tag in a PodSpec
301
// may be multiple tags in a file
302
func getPodImages(k8sObj string) []string {
303
var images []string
304
305
re := regexp.MustCompile("image:(.*)")
306
img := re.FindAllString(k8sObj, -1)
307
308
if len(img) > 0 {
309
for _, i := range img {
310
// Remove "image":
311
i = re.ReplaceAllString(i, "$1")
312
// Remove whitespace
313
i = strings.TrimSpace(i)
314
// Remove wrapping quotes
315
i = strings.Trim(i, "\"")
316
317
images = append(images, i)
318
}
319
}
320
321
return images
322
}
323
324