Skip to content

Commit e468aa6

Browse files
Merge pull request openshift#2005 from bfournie/node-image-disconnected
OCPBUGS-53106: Get installer image from configMap for node-image command
2 parents 306c7b3 + e785684 commit e468aa6

File tree

3 files changed

+341
-1
lines changed

3 files changed

+341
-1
lines changed

pkg/cli/admin/nodeimage/basenodeimagecommand.go

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"fmt"
77
"io"
8+
"os"
89
"regexp"
910
"strings"
1011
"time"
@@ -23,6 +24,7 @@ import (
2324
"k8s.io/client-go/kubernetes"
2425
"k8s.io/client-go/rest"
2526
"k8s.io/kubectl/pkg/cmd/exec"
27+
"sigs.k8s.io/yaml"
2628

2729
ocpv1 "github.com/openshift/api/config/v1"
2830
configclient "github.com/openshift/client-go/config/clientset/versioned"
@@ -87,9 +89,43 @@ func (c *BaseNodeImageCommand) getNodeJoinerPullSpec(ctx context.Context) error
8789
}
8890

8991
// Extract the baremetal-installer image pullspec, since it
90-
// provide the node-joiner tool.
92+
// provides the node-joiner tool.
93+
94+
// First attempt to get the installer image from the configMap created by installer. This will work in disconnected environments.
95+
installerImagesConfigMap, err := c.Client.CoreV1().ConfigMaps("openshift-config").Get(ctx, "installer-images", metav1.GetOptions{})
96+
if err == nil {
97+
images := installerImagesConfigMap.Data["images.json"]
98+
if len(images) > 0 {
99+
imageMap := make(map[string]string)
100+
err := yaml.Unmarshal([]byte(images), &imageMap)
101+
if err == nil {
102+
installer, exists := imageMap["installer"]
103+
if exists {
104+
// Found pullSpec from configMap
105+
c.log("installer pullspec obtained from installer-images configMap %s", installer)
106+
c.nodeJoinerImage = installer
107+
return nil
108+
}
109+
}
110+
}
111+
}
112+
113+
// If cannot obtain installer image from configMap, get it from the releaseImage.
114+
// Note that after OCP 4.20, this should not be necessary since the configMap was added to installer in 4.19.
115+
c.log("configMap containing installer-images is not available, trying to get image from registry")
91116
opts := ocrelease.NewInfoOptions(c.IOStreams)
92117
opts.SecurityOptions = c.SecurityOptions
118+
idmsFile, err := c.getIdmsFile(ctx)
119+
if err != nil {
120+
return err
121+
}
122+
opts.IDMSFile = idmsFile
123+
defer func() {
124+
if opts.IDMSFile != "" {
125+
os.Remove(opts.IDMSFile)
126+
}
127+
}()
128+
93129
release, err := opts.LoadReleaseInfo(releaseImage, false)
94130
if err != nil {
95131
return err
@@ -409,3 +445,45 @@ func (o *BaseNodeImageCommand) runNodeJoinerPod(ctx context.Context, tasks []fun
409445
}
410446
return nil
411447
}
448+
449+
func (c *BaseNodeImageCommand) getIdmsFile(ctx context.Context) (string, error) {
450+
imageDigestMirrorSets, idmsErr := c.ConfigClient.ConfigV1().ImageDigestMirrorSets().List(ctx, metav1.ListOptions{})
451+
if idmsErr != nil && !kapierrors.IsNotFound(idmsErr) {
452+
return "", idmsErr
453+
}
454+
imageContentPolicies, icspErr := c.ConfigClient.ConfigV1().ImageContentPolicies().List(ctx, metav1.ListOptions{})
455+
if icspErr != nil && !kapierrors.IsNotFound(icspErr) {
456+
return "", icspErr
457+
}
458+
if idmsErr != nil || len(imageDigestMirrorSets.Items) == 0 {
459+
imageDigestMirrorSets = nil // handle empty idms
460+
}
461+
if icspErr != nil || len(imageContentPolicies.Items) == 0 {
462+
imageContentPolicies = nil // handle empty icsp
463+
}
464+
if imageDigestMirrorSets == nil && imageContentPolicies == nil {
465+
return "", nil
466+
}
467+
468+
// Build IDMS file from contents of the IDMS and ICSP mirror digests
469+
contents, err := getIdmsContents(c.LogOut, imageDigestMirrorSets, imageContentPolicies)
470+
if err != nil {
471+
c.log("failure parsing imageDigestMirrorSet %s", err.Error())
472+
return "", err
473+
}
474+
475+
// create temp file which will be removed by caller
476+
idmsFile, err := os.CreateTemp("", "idms-file")
477+
if err != nil {
478+
return "", err
479+
}
480+
481+
c.log("Building IDMS file with contents %s", contents)
482+
if _, err := idmsFile.Write(contents); err != nil {
483+
idmsFile.Close()
484+
os.Remove(idmsFile.Name())
485+
return "", err
486+
}
487+
idmsFile.Close()
488+
return idmsFile.Name(), nil
489+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package nodeimage
2+
3+
import (
4+
"fmt"
5+
"io"
6+
7+
ocpv1 "github.com/openshift/api/config/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"sigs.k8s.io/yaml"
10+
)
11+
12+
func getIdmsContents(writer io.Writer, idmsMirrorSetList *ocpv1.ImageDigestMirrorSetList, icspMirrorSetList *ocpv1.ImageContentPolicyList) ([]byte, error) {
13+
imageDigestSet := ocpv1.ImageDigestMirrorSet{
14+
TypeMeta: metav1.TypeMeta{
15+
APIVersion: ocpv1.GroupVersion.String(),
16+
Kind: "ImageDigestMirrorSet",
17+
},
18+
ObjectMeta: metav1.ObjectMeta{
19+
Name: "image-digest",
20+
// not namespaced
21+
},
22+
}
23+
24+
// Add mirrors from IDMS
25+
if idmsMirrorSetList != nil {
26+
log(writer, "Adding mirrors from ImageDigestMirrors")
27+
for _, idms := range idmsMirrorSetList.Items {
28+
for _, digestMirror := range idms.Spec.ImageDigestMirrors {
29+
imageDigestSet.Spec.ImageDigestMirrors = append(imageDigestSet.Spec.ImageDigestMirrors, digestMirror)
30+
}
31+
}
32+
}
33+
34+
// Add mirrors from ICSP
35+
if icspMirrorSetList != nil {
36+
log(writer, "Adding mirrors from ImageContentPolicies")
37+
for _, icsp := range icspMirrorSetList.Items {
38+
for _, digestMirror := range icsp.Spec.RepositoryDigestMirrors {
39+
// Skip adding the mirror if the source already exists
40+
if digestSourceExists(imageDigestSet.Spec.ImageDigestMirrors, digestMirror.Source) {
41+
continue
42+
}
43+
mirror := ocpv1.ImageDigestMirrors{
44+
Source: digestMirror.Source,
45+
}
46+
for _, m := range digestMirror.Mirrors {
47+
mirror.Mirrors = append(mirror.Mirrors, ocpv1.ImageMirror(m))
48+
}
49+
imageDigestSet.Spec.ImageDigestMirrors = append(imageDigestSet.Spec.ImageDigestMirrors, mirror)
50+
}
51+
}
52+
}
53+
54+
contents, err := yaml.Marshal(imageDigestSet)
55+
if err != nil {
56+
return nil, err
57+
}
58+
59+
return contents, nil
60+
}
61+
62+
func digestSourceExists(mirrors []ocpv1.ImageDigestMirrors, source string) bool {
63+
for _, mirror := range mirrors {
64+
if mirror.Source == source {
65+
return true
66+
}
67+
}
68+
return false
69+
}
70+
71+
func log(writer io.Writer, format string, a ...interface{}) {
72+
if writer != nil {
73+
fmt.Fprintf(writer, format+"\n", a...)
74+
}
75+
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package nodeimage
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
ocpv1 "github.com/openshift/api/config/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
)
10+
11+
var defaultIdmsSetList = ocpv1.ImageDigestMirrorSetList{
12+
TypeMeta: metav1.TypeMeta{},
13+
ListMeta: metav1.ListMeta{
14+
ResourceVersion: "15",
15+
},
16+
Items: []ocpv1.ImageDigestMirrorSet{
17+
{
18+
TypeMeta: metav1.TypeMeta{},
19+
ObjectMeta: metav1.ObjectMeta{Name: "image-digest-mirror", Namespace: "test"},
20+
Spec: ocpv1.ImageDigestMirrorSetSpec{
21+
ImageDigestMirrors: []ocpv1.ImageDigestMirrors{
22+
{
23+
Source: "quay.io/openshift-release-dev/ocp-v4.0-art-dev",
24+
Mirrors: []ocpv1.ImageMirror{
25+
"virthost.test:5000/localimages/local-release-image",
26+
},
27+
},
28+
{
29+
Source: "registry.ci.openshift.org/ocp/release",
30+
Mirrors: []ocpv1.ImageMirror{
31+
"virthost.test:5000/localimages/local-dev",
32+
},
33+
},
34+
},
35+
},
36+
},
37+
},
38+
}
39+
40+
var defaultIdms = `apiVersion: config.openshift.io/v1
41+
kind: ImageDigestMirrorSet
42+
metadata:
43+
creationTimestamp: null
44+
name: image-digest
45+
spec:
46+
imageDigestMirrors:
47+
- mirrors:
48+
- virthost.test:5000/localimages/local-release-image
49+
source: quay.io/openshift-release-dev/ocp-v4.0-art-dev
50+
- mirrors:
51+
- virthost.test:5000/localimages/local-dev
52+
source: registry.ci.openshift.org/ocp/release
53+
status: {}
54+
`
55+
56+
var defaultIcspList = ocpv1.ImageContentPolicyList{
57+
TypeMeta: metav1.TypeMeta{},
58+
ListMeta: metav1.ListMeta{
59+
ResourceVersion: "15",
60+
},
61+
Items: []ocpv1.ImageContentPolicy{
62+
{
63+
TypeMeta: metav1.TypeMeta{},
64+
ObjectMeta: metav1.ObjectMeta{Name: "image-policy", Namespace: "test"},
65+
Spec: ocpv1.ImageContentPolicySpec{
66+
RepositoryDigestMirrors: []ocpv1.RepositoryDigestMirrors{
67+
{
68+
Source: "quay.io/openshift-release-dev/ocp-v4.0-art-dev",
69+
Mirrors: []ocpv1.Mirror{
70+
"virthost.test:5000/localimages/local-release-image",
71+
},
72+
},
73+
{
74+
Source: "registry.ci.openshift.org/ocp/release",
75+
Mirrors: []ocpv1.Mirror{
76+
"virthost.test:5000/localimages/local-dev",
77+
},
78+
},
79+
},
80+
},
81+
},
82+
},
83+
}
84+
85+
var icspListDifferentThanIdms = ocpv1.ImageContentPolicyList{
86+
TypeMeta: metav1.TypeMeta{},
87+
ListMeta: metav1.ListMeta{
88+
ResourceVersion: "15",
89+
},
90+
Items: []ocpv1.ImageContentPolicy{
91+
{
92+
TypeMeta: metav1.TypeMeta{},
93+
ObjectMeta: metav1.ObjectMeta{Name: "image-policy", Namespace: "test"},
94+
Spec: ocpv1.ImageContentPolicySpec{
95+
RepositoryDigestMirrors: []ocpv1.RepositoryDigestMirrors{
96+
{
97+
Source: "example.com/ocp-v4.0-art-dev",
98+
Mirrors: []ocpv1.Mirror{
99+
"localimages/local-release-image",
100+
},
101+
},
102+
{
103+
Source: "example.com/ocp/release",
104+
Mirrors: []ocpv1.Mirror{
105+
"localimages/local-dev",
106+
},
107+
},
108+
},
109+
},
110+
},
111+
},
112+
}
113+
114+
var icspPlusIdms = `apiVersion: config.openshift.io/v1
115+
kind: ImageDigestMirrorSet
116+
metadata:
117+
creationTimestamp: null
118+
name: image-digest
119+
spec:
120+
imageDigestMirrors:
121+
- mirrors:
122+
- virthost.test:5000/localimages/local-release-image
123+
source: quay.io/openshift-release-dev/ocp-v4.0-art-dev
124+
- mirrors:
125+
- virthost.test:5000/localimages/local-dev
126+
source: registry.ci.openshift.org/ocp/release
127+
- mirrors:
128+
- localimages/local-release-image
129+
source: example.com/ocp-v4.0-art-dev
130+
- mirrors:
131+
- localimages/local-dev
132+
source: example.com/ocp/release
133+
status: {}
134+
`
135+
136+
func TestGetIdmsContents(t *testing.T) {
137+
testCases := []struct {
138+
name string
139+
idmsSetList *ocpv1.ImageDigestMirrorSetList
140+
icspSetList *ocpv1.ImageContentPolicyList
141+
expectedIdms []byte
142+
expectedError string
143+
}{
144+
{
145+
name: "IDMS Only mirrors",
146+
idmsSetList: &defaultIdmsSetList,
147+
icspSetList: nil,
148+
expectedIdms: []byte(defaultIdms),
149+
},
150+
{
151+
name: "ICSP Only mirrors",
152+
idmsSetList: nil,
153+
icspSetList: &defaultIcspList,
154+
expectedIdms: []byte(defaultIdms),
155+
},
156+
{
157+
name: "IDMS and ICSP mirrors the same",
158+
idmsSetList: &defaultIdmsSetList,
159+
icspSetList: &defaultIcspList,
160+
expectedIdms: []byte(defaultIdms),
161+
},
162+
{
163+
name: "IDMS and ICSP mirrors are different",
164+
idmsSetList: &defaultIdmsSetList,
165+
icspSetList: &icspListDifferentThanIdms,
166+
expectedIdms: []byte(icspPlusIdms),
167+
},
168+
}
169+
for _, tc := range testCases {
170+
t.Run(tc.name, func(t *testing.T) {
171+
idmsContents, err := getIdmsContents(nil, tc.idmsSetList, tc.icspSetList)
172+
173+
if tc.expectedError == "" {
174+
if err != nil {
175+
t.Fatalf("unexpected error: %v", err)
176+
}
177+
if !reflect.DeepEqual(tc.expectedIdms, idmsContents) {
178+
t.Errorf("Expected:\n%s\ngot:\n%s", tc.expectedIdms, idmsContents)
179+
}
180+
} else {
181+
if err == nil {
182+
t.Fatalf("expected error not received: %s", tc.expectedError)
183+
}
184+
}
185+
})
186+
}
187+
}

0 commit comments

Comments
 (0)