Skip to content

Commit ad5937e

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 4951f52 commit ad5937e

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed

installer/cmd/mirror.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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+
"github.com/spf13/cobra"
9+
)
10+
11+
// validateCmd represents the validate command
12+
var mirrorCmd = &cobra.Command{
13+
Use: "mirror",
14+
Short: "Performs mirroring tasks",
15+
}
16+
17+
func init() {
18+
rootCmd.AddCommand(mirrorCmd)
19+
}

installer/cmd/mirror_list.go

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

0 commit comments

Comments
 (0)