Skip to content

Commit b333571

Browse files
authored
internal/pkg/action/catalog_add.go: several improvements (#21)
- When pulling images and image labels for an index image that is a manifest list, try all platforms rather than just the platform on which the `kubectl operator` command is running. This fixes an issue with using the `catalog add` command on macOS with manifest list-based index images - When using bundle injection use the presence of the database path label to determine if the image is an empty base index rather than hardcoding the known empty base index images. Also use the value from the database path label instead of hardcoding the database path. - Change display name and publisher labels to an alpha subdomain: * alpha.operators.operatorframework.io.index.display-name.v1 * alpha.operators.operatorframework.io.index.publisher.v1 - Adding an owner reference to the index image registry pod no longer requires an extra Update call to the API server
1 parent 1f47404 commit b333571

File tree

2 files changed

+82
-56
lines changed

2 files changed

+82
-56
lines changed

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ module github.com/operator-framework/kubectl-operator
33
go 1.13
44

55
require (
6+
github.com/containerd/containerd v1.3.2
7+
github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6
68
github.com/operator-framework/api v0.3.7
79
github.com/operator-framework/operator-lifecycle-manager v0.0.0-20200521062108-408ca95d458f
810
github.com/operator-framework/operator-registry v1.12.5

internal/pkg/action/catalog_add.go

+80-56
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,16 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"io"
8+
"path"
79
"strings"
810
"time"
911

12+
"github.com/containerd/containerd/archive/compression"
13+
"github.com/containerd/containerd/images"
14+
"github.com/containerd/containerd/namespaces"
15+
"github.com/containerd/containerd/platforms"
16+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
1017
"github.com/operator-framework/api/pkg/operators/v1alpha1"
1118
"github.com/operator-framework/operator-registry/pkg/image"
1219
"github.com/operator-framework/operator-registry/pkg/image/containerdregistry"
@@ -23,7 +30,13 @@ import (
2330
"github.com/operator-framework/kubectl-operator/internal/pkg/catalog"
2431
)
2532

26-
const grpcPort = "50051"
33+
const (
34+
grpcPort = "50051"
35+
dbPathLabel = "operators.operatorframework.io.index.database.v1"
36+
alphaDisplayNameLabel = "alpha.operators.operatorframework.io.index.display-name.v1"
37+
alphaPublisherLabel = "alpha.operators.operatorframework.io.index.publisher.v1"
38+
defaultDatabasePath = "/database/index.db"
39+
)
2740

2841
type CatalogAdd struct {
2942
config *Configuration
@@ -81,7 +94,7 @@ func (a *CatalogAdd) Run(ctx context.Context) (*v1alpha1.CatalogSource, error) {
8194

8295
labels, err := a.labelsFor(ctx, a.IndexImage)
8396
if err != nil {
84-
return nil, err
97+
return nil, fmt.Errorf("get image labels: %v", err)
8598
}
8699

87100
a.setDefaults(labels)
@@ -96,20 +109,33 @@ func (a *CatalogAdd) Run(ctx context.Context) (*v1alpha1.CatalogSource, error) {
96109
}
97110

98111
cs := catalog.Build(csKey, opts...)
99-
if err := a.createCatalogSource(ctx, cs); err != nil {
100-
return nil, err
112+
if err := a.config.Client.Create(ctx, cs); err != nil {
113+
return nil, fmt.Errorf("create catalogsource: %v", err)
101114
}
102115

103116
var registryPod *corev1.Pod
104117
if len(a.InjectBundles) > 0 {
105-
if registryPod, err = a.createRegistryPod(ctx, cs); err != nil {
118+
dbPath, ok := labels[dbPathLabel]
119+
if !ok {
120+
// No database path label, so assume this is an index base image.
121+
// Choose "semver" bundle add mode (if not explicitly set) and
122+
// use the default database path.
123+
if a.InjectBundleMode == "" {
124+
a.InjectBundleMode = "semver"
125+
}
126+
dbPath = defaultDatabasePath
127+
}
128+
if a.InjectBundleMode == "" {
129+
a.InjectBundleMode = "replaces"
130+
}
131+
if registryPod, err = a.createRegistryPod(ctx, cs, dbPath); err != nil {
106132
defer a.cleanup(cs)
107133
return nil, err
108134
}
109135

110136
if err := a.updateCatalogSource(ctx, cs, registryPod); err != nil {
111137
defer a.cleanup(cs)
112-
return nil, err
138+
return nil, fmt.Errorf("update catalog source: %v", err)
113139
}
114140
}
115141

@@ -122,51 +148,61 @@ func (a *CatalogAdd) Run(ctx context.Context) (*v1alpha1.CatalogSource, error) {
122148
}
123149

124150
func (a *CatalogAdd) labelsFor(ctx context.Context, indexImage string) (map[string]string, error) {
125-
simpleRef := image.SimpleReference(indexImage)
126-
if err := a.registry.Pull(ctx, simpleRef); err != nil {
151+
ref := image.SimpleReference(indexImage)
152+
if err := a.registry.Pull(ctx, ref); err != nil {
127153
return nil, fmt.Errorf("pull image: %v", err)
128154
}
129-
labels, err := a.registry.Labels(ctx, simpleRef)
155+
156+
ctx = namespaces.WithNamespace(ctx, namespaces.Default)
157+
img, err := a.registry.Images().Get(ctx, ref.String())
130158
if err != nil {
131-
return nil, fmt.Errorf("get image labels: %v", err)
159+
return nil, fmt.Errorf("get image from local registry: %v", err)
132160
}
133-
return labels, nil
161+
162+
manifest, err := images.Manifest(ctx, a.registry.Content(), img.Target, platforms.All)
163+
if err != nil {
164+
return nil, fmt.Errorf("resolve image manifest: %v", err)
165+
}
166+
167+
ra, err := a.registry.Content().ReaderAt(ctx, manifest.Config)
168+
if err != nil {
169+
return nil, fmt.Errorf("get image reader: %v", err)
170+
}
171+
defer ra.Close()
172+
173+
decompressed, err := compression.DecompressStream(io.NewSectionReader(ra, 0, ra.Size()))
174+
if err != nil {
175+
return nil, fmt.Errorf("decompress image data: %v", err)
176+
}
177+
var imageMeta ocispec.Image
178+
dec := json.NewDecoder(decompressed)
179+
if err := dec.Decode(&imageMeta); err != nil {
180+
return nil, fmt.Errorf("decode image metadata: %v", err)
181+
}
182+
return imageMeta.Config.Labels, nil
134183
}
135184

136185
func (a *CatalogAdd) setDefaults(labels map[string]string) {
137186
if a.DisplayName == "" {
138-
if v, ok := labels["operators.operatorframework.io.index.display-name"]; ok {
187+
if v, ok := labels[alphaDisplayNameLabel]; ok {
139188
a.DisplayName = v
140189
}
141190
}
142191
if a.Publisher == "" {
143-
if v, ok := labels["operators.operatorframework.io.index.publisher"]; ok {
192+
if v, ok := labels[alphaPublisherLabel]; ok {
144193
a.Publisher = v
145194
}
146195
}
147-
if a.InjectBundleMode == "" {
148-
if strings.HasPrefix(a.IndexImage, "quay.io/operator-framework/upstream-opm-builder") {
149-
a.InjectBundleMode = "semver"
150-
} else {
151-
a.InjectBundleMode = "replaces"
152-
}
153-
}
154-
}
155-
156-
func (a *CatalogAdd) createCatalogSource(ctx context.Context, cs *v1alpha1.CatalogSource) error {
157-
if err := a.config.Client.Create(ctx, cs); err != nil {
158-
return fmt.Errorf("create catalogsource: %v", err)
159-
}
160-
return nil
161196
}
162197

163-
func (a *CatalogAdd) createRegistryPod(ctx context.Context, cs *v1alpha1.CatalogSource) (*corev1.Pod, error) {
198+
func (a *CatalogAdd) createRegistryPod(ctx context.Context, cs *v1alpha1.CatalogSource, dbPath string) (*corev1.Pod, error) {
199+
dbDir := path.Dir(dbPath)
164200
command := []string{
165201
"/bin/sh",
166202
"-c",
167-
fmt.Sprintf(`mkdir -p /database && \
168-
/bin/opm registry add -d /database/index.db --mode=%s -b %s && \
169-
/bin/opm registry serve -d /database/index.db -p %s`, a.InjectBundleMode, strings.Join(a.InjectBundles, ","), grpcPort),
203+
fmt.Sprintf(`mkdir -p %s && \
204+
/bin/opm registry add -d %s --mode=%s -b %s && \
205+
/bin/opm registry serve -d %s -p %s`, dbDir, dbPath, a.InjectBundleMode, strings.Join(a.InjectBundles, ","), dbPath, grpcPort),
170206
}
171207

172208
pod := &corev1.Pod{
@@ -184,26 +220,15 @@ func (a *CatalogAdd) createRegistryPod(ctx context.Context, cs *v1alpha1.Catalog
184220
},
185221
},
186222
}
223+
224+
if err := controllerutil.SetOwnerReference(cs, pod, a.config.Scheme); err != nil {
225+
return nil, fmt.Errorf("set registry pod owner reference: %v", err)
226+
}
187227
if err := a.config.Client.Create(ctx, pod); err != nil {
188228
return nil, fmt.Errorf("create registry pod: %v", err)
189229
}
190230

191231
podKey := objectKeyForObject(pod)
192-
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
193-
if err := a.config.Client.Get(ctx, podKey, pod); err != nil {
194-
return fmt.Errorf("get registry pod: %v", err)
195-
}
196-
if err := controllerutil.SetOwnerReference(cs, pod, a.config.Scheme); err != nil {
197-
return fmt.Errorf("set registry pod owner reference: %v", err)
198-
}
199-
if err := a.config.Client.Update(ctx, pod); err != nil {
200-
return fmt.Errorf("update registry pod owner reference: %v", err)
201-
}
202-
return nil
203-
}); err != nil {
204-
return nil, err
205-
}
206-
207232
if err := wait.PollImmediateUntil(time.Millisecond*250, func() (bool, error) {
208233
if err := a.config.Client.Get(ctx, podKey, pod); err != nil {
209234
return false, err
@@ -219,26 +244,25 @@ func (a *CatalogAdd) createRegistryPod(ctx context.Context, cs *v1alpha1.Catalog
219244
}
220245

221246
func (a *CatalogAdd) updateCatalogSource(ctx context.Context, cs *v1alpha1.CatalogSource, pod *corev1.Pod) error {
222-
cs.Spec.Address = fmt.Sprintf("%s:%s", pod.Status.PodIP, grpcPort)
223-
224247
injectedBundlesJSON, err := json.Marshal(a.InjectBundles)
225248
if err != nil {
226249
return fmt.Errorf("json marshal injected bundles: %v", err)
227250
}
228-
cs.ObjectMeta.Annotations = map[string]string{
229-
"operators.operatorframework.io/index-image": a.IndexImage,
230-
"operators.operatorframework.io/inject-bundle-mode": a.InjectBundleMode,
231-
"operators.operatorframework.io/injected-bundles": string(injectedBundlesJSON),
232-
}
251+
233252
csKey := objectKeyForObject(cs)
234253
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
235254
if err := a.config.Client.Get(ctx, csKey, cs); err != nil {
236255
return fmt.Errorf("get catalog source: %v", err)
237256
}
238-
if err := a.config.Client.Update(ctx, cs); err != nil {
239-
return fmt.Errorf("update catalog source: %v", err)
257+
258+
cs.Spec.Address = fmt.Sprintf("%s:%s", pod.Status.PodIP, grpcPort)
259+
cs.ObjectMeta.Annotations = map[string]string{
260+
"operators.operatorframework.io/index-image": a.IndexImage,
261+
"operators.operatorframework.io/inject-bundle-mode": a.InjectBundleMode,
262+
"operators.operatorframework.io/injected-bundles": string(injectedBundlesJSON),
240263
}
241-
return nil
264+
265+
return a.config.Client.Update(ctx, cs)
242266
}); err != nil {
243267
return err
244268
}

0 commit comments

Comments
 (0)