Skip to content

Commit 93af1c2

Browse files
author
Simon Emms
committed
[installer]: output all images to allow easy mirroring of containers
The output gives the original image name and tag and generates the new image name to be used
1 parent 0ffc007 commit 93af1c2

File tree

1 file changed

+220
-0
lines changed

1 file changed

+220
-0
lines changed

installer/cmd/render-mirror.go

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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+
"encoding/json"
9+
"fmt"
10+
"regexp"
11+
"sort"
12+
"strings"
13+
14+
"github.com/docker/distribution/reference"
15+
"github.com/gitpod-io/gitpod/installer/pkg/common"
16+
"github.com/spf13/cobra"
17+
"sigs.k8s.io/yaml"
18+
)
19+
20+
type mirrorRepo struct {
21+
Original string `json:"original"`
22+
Target string `json:"target"`
23+
}
24+
25+
var mirrorOpts struct {
26+
ExcludeThirdParty bool
27+
Output string
28+
}
29+
30+
var outputTypes = []string{"yaml", "json"}
31+
32+
// mirrorCmd represents the mirror command
33+
var mirrorCmd = &cobra.Command{
34+
Use: "mirror",
35+
Short: "Renders a list of images used so they can be mirrored to a third-party registry",
36+
Long: `Renders a list of images used so they can be mirrored to a third-party registry
37+
38+
A config file is required which can be generated with the init command.
39+
The "repository" field must be set to your container repository server
40+
address and this value will be used to generate the mirrored image names
41+
and tags.
42+
43+
The output can then be used to iterate over each image. A script can
44+
be written to pull from the "original" path and then tag and push the
45+
image to the "target" repo`,
46+
Example: `
47+
# YAML
48+
gitpod-installer render mirror --config config.yaml > mirror.yaml
49+
50+
# JSON
51+
gitpod-installer render mirror --config config.yaml -o json > mirror.json
52+
53+
# Pull original and push to target
54+
for row in $(gitpod-installer render mirror --config ./config.yaml -o json | jq -c '.[]'); do
55+
original=$(echo $row | jq -r '.original')
56+
target=$(echo $row | jq -r '.target')
57+
docker pull $original
58+
docker tag $original $target
59+
docker push $target
60+
done`,
61+
RunE: func(cmd *cobra.Command, args []string) error {
62+
// Validate output type
63+
var output string
64+
for _, i := range outputTypes {
65+
if mirrorOpts.Output == i {
66+
output = i
67+
}
68+
}
69+
if output == "" {
70+
return fmt.Errorf("unknown output type: %s", mirrorOpts.Output)
71+
}
72+
73+
_, cfgVersion, cfg, err := loadConfig(renderOpts.ConfigFN)
74+
if err != nil {
75+
return err
76+
}
77+
78+
// Throw error if set to the default Gitpod repository
79+
if cfg.Repository == common.GitpodContainerRegistry {
80+
return fmt.Errorf("cannot mirror images to repository %s", common.GitpodContainerRegistry)
81+
}
82+
83+
// Get the target repository from the config
84+
targetRepo := strings.TrimRight(cfg.Repository, "/")
85+
86+
// Use the default Gitpod registry to pull from
87+
cfg.Repository = common.GitpodContainerRegistry
88+
89+
k8s, err := renderKubernetesObjects(cfgVersion, cfg)
90+
if err != nil {
91+
return err
92+
}
93+
94+
// Map of images used for deduping
95+
allImages := make(map[string]bool)
96+
97+
rawImages := make([]string, 0)
98+
for _, item := range k8s {
99+
rawImages = append(rawImages, getPodImages(item)...)
100+
rawImages = append(rawImages, getGenericImages(item)...)
101+
}
102+
103+
images := make([]mirrorRepo, 0)
104+
for _, img := range rawImages {
105+
// Dedupe
106+
if _, ok := allImages[img]; ok {
107+
continue
108+
}
109+
allImages[img] = true
110+
111+
// Convert target
112+
target := img
113+
if strings.Contains(img, cfg.Repository) {
114+
// This is the Gitpod registry
115+
target = strings.Replace(target, cfg.Repository, targetRepo, 1)
116+
} else if !mirrorOpts.ExcludeThirdParty {
117+
// Amend third-party images - remove the first part
118+
thirdPartyImg := strings.Join(strings.Split(img, "/")[1:], "/")
119+
target = fmt.Sprintf("%s/%s", targetRepo, thirdPartyImg)
120+
} else {
121+
// Excluding third-party images - just skip this one
122+
continue
123+
}
124+
125+
images = append(images, mirrorRepo{
126+
Original: img,
127+
Target: target,
128+
})
129+
}
130+
131+
// Sort it by the Original
132+
sort.Slice(images, func(i, j int) bool {
133+
scoreI := images[i].Original
134+
scoreJ := images[j].Original
135+
136+
return scoreI < scoreJ
137+
})
138+
139+
switch output {
140+
case "yaml":
141+
fc, err := yaml.Marshal(images)
142+
if err != nil {
143+
return err
144+
}
145+
146+
fmt.Printf(string(fc))
147+
case "json":
148+
fc, err := json.MarshalIndent(images, "", " ")
149+
if err != nil {
150+
return err
151+
}
152+
153+
fmt.Println(string(fc))
154+
}
155+
156+
return nil
157+
},
158+
}
159+
160+
func init() {
161+
renderCmd.AddCommand(mirrorCmd)
162+
163+
mirrorCmd.Flags().BoolVar(&mirrorOpts.ExcludeThirdParty, "exclude-third-party", false, "exclude non-Gitpod images")
164+
mirrorCmd.Flags().StringVarP(&mirrorOpts.Output, "output", "o", "yaml", fmt.Sprintf("output type - [%s]", strings.Join(outputTypes, ", ")))
165+
}
166+
167+
// getGenericImages this is a bit brute force - anything starting "docker.io" or with Gitpod repo is found
168+
// this will be in ConfigMaps and could be anything, so will need cleaning up
169+
func getGenericImages(k8sObj string) []string {
170+
var images []string
171+
172+
// Search for anything that matches docker.io or the Gitpod repo - docker.io needed for gitpod/workspace-full
173+
re := regexp.MustCompile(fmt.Sprintf("%s(.*)|%s(.*)", "docker.io", common.GitpodContainerRegistry))
174+
img := re.FindAllString(k8sObj, -1)
175+
176+
if len(img) > 0 {
177+
for _, i := range img {
178+
// Remove whitespace
179+
i = strings.TrimSpace(i)
180+
// Remove end commas
181+
i = strings.TrimRight(i, ",")
182+
// Remove wrapping quotes
183+
i = strings.Trim(i, "\"")
184+
// Validate the image - assumes images are already fully qualified names
185+
_, err := reference.ParseNamed(i)
186+
if err != nil {
187+
// Invalid - ignore
188+
continue
189+
}
190+
191+
images = append(images, i)
192+
}
193+
}
194+
195+
return images
196+
}
197+
198+
// getPodImages these are images that are found in the "image:" tag in a PodSpec
199+
// may be multiple tags in a file
200+
func getPodImages(k8sObj string) []string {
201+
var images []string
202+
203+
re := regexp.MustCompile("image:(.*)")
204+
img := re.FindAllString(k8sObj, -1)
205+
206+
if len(img) > 0 {
207+
for _, i := range img {
208+
// Remove "image":
209+
i = re.ReplaceAllString(i, "$1")
210+
// Remove whitespace
211+
i = strings.TrimSpace(i)
212+
// Remove wrapping quotes
213+
i = strings.Trim(i, "\"")
214+
215+
images = append(images, i)
216+
}
217+
}
218+
219+
return images
220+
}

0 commit comments

Comments
 (0)