Skip to content

Commit 0917019

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 0917019

File tree

1 file changed

+217
-0
lines changed

1 file changed

+217
-0
lines changed

installer/cmd/render-mirror.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
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+
// Wrap third-party images - remove the first part
118+
thirdPartyImg := strings.Join(strings.Split(img, "/")[1:], "/")
119+
target = fmt.Sprintf("%s/%s", targetRepo, thirdPartyImg)
120+
}
121+
122+
images = append(images, mirrorRepo{
123+
Original: img,
124+
Target: target,
125+
})
126+
}
127+
128+
// Sort it by the Original
129+
sort.Slice(images, func(i, j int) bool {
130+
scoreI := images[i].Original
131+
scoreJ := images[j].Original
132+
133+
return scoreI < scoreJ
134+
})
135+
136+
switch output {
137+
case "yaml":
138+
fc, err := yaml.Marshal(images)
139+
if err != nil {
140+
return err
141+
}
142+
143+
fmt.Printf("---\n# Gitpod\n%s", string(fc))
144+
case "json":
145+
fc, err := json.MarshalIndent(images, "", " ")
146+
if err != nil {
147+
return err
148+
}
149+
150+
fmt.Println(string(fc))
151+
}
152+
153+
return nil
154+
},
155+
}
156+
157+
func init() {
158+
renderCmd.AddCommand(mirrorCmd)
159+
160+
mirrorCmd.Flags().BoolVar(&mirrorOpts.ExcludeThirdParty, "exclude-third-party", false, "exclude non-Gitpod images")
161+
mirrorCmd.Flags().StringVarP(&mirrorOpts.Output, "output", "o", "yaml", fmt.Sprintf("output type - [%s]", strings.Join(outputTypes, ", ")))
162+
}
163+
164+
// getGenericImages this is a bit brute force - anything starting "docker.io" or with Gitpod repo is found
165+
// this will be in ConfigMaps and could be anything, so will need cleaning up
166+
func getGenericImages(k8sObj string) []string {
167+
var images []string
168+
169+
// Search for anything that matches docker.io or the Gitpod repo - docker.io needed for gitpod/workspace-full
170+
re := regexp.MustCompile(fmt.Sprintf("%s(.*)|%s(.*)", "docker.io", common.GitpodContainerRegistry))
171+
img := re.FindAllString(k8sObj, -1)
172+
173+
if len(img) > 0 {
174+
for _, i := range img {
175+
// Remove whitespace
176+
i = strings.TrimSpace(i)
177+
// Remove end commas
178+
i = strings.TrimRight(i, ",")
179+
// Remove wrapping quotes
180+
i = strings.Trim(i, "\"")
181+
// Validate the image - assumes images are already fully qualified names
182+
_, err := reference.ParseNamed(i)
183+
if err != nil {
184+
// Invalid - ignore
185+
continue
186+
}
187+
188+
images = append(images, i)
189+
}
190+
}
191+
192+
return images
193+
}
194+
195+
// getPodImages these are images that are found in the "image:" tag in a PodSpec
196+
// may be multiple tags in a file
197+
func getPodImages(k8sObj string) []string {
198+
var images []string
199+
200+
re := regexp.MustCompile("image:(.*)")
201+
img := re.FindAllString(k8sObj, -1)
202+
203+
if len(img) > 0 {
204+
for _, i := range img {
205+
// Remove "image":
206+
i = re.ReplaceAllString(i, "$1")
207+
// Remove whitespace
208+
i = strings.TrimSpace(i)
209+
// Remove wrapping quotes
210+
i = strings.Trim(i, "\"")
211+
212+
images = append(images, i)
213+
}
214+
}
215+
216+
return images
217+
}

0 commit comments

Comments
 (0)