diff --git a/go.mod b/go.mod index 87a84e8..4068510 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/operator-framework/kubectl-operator go 1.13 require ( + github.com/containerd/containerd v1.3.2 + github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 github.com/operator-framework/api v0.3.7 github.com/operator-framework/operator-lifecycle-manager v0.0.0-20200521062108-408ca95d458f github.com/operator-framework/operator-registry v1.12.5 diff --git a/internal/pkg/action/catalog_add.go b/internal/pkg/action/catalog_add.go index 006178f..2ddd187 100644 --- a/internal/pkg/action/catalog_add.go +++ b/internal/pkg/action/catalog_add.go @@ -4,9 +4,16 @@ import ( "context" "encoding/json" "fmt" + "io" + "path" "strings" "time" + "github.com/containerd/containerd/archive/compression" + "github.com/containerd/containerd/images" + "github.com/containerd/containerd/namespaces" + "github.com/containerd/containerd/platforms" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/operator-registry/pkg/image" "github.com/operator-framework/operator-registry/pkg/image/containerdregistry" @@ -23,7 +30,13 @@ import ( "github.com/operator-framework/kubectl-operator/internal/pkg/catalog" ) -const grpcPort = "50051" +const ( + grpcPort = "50051" + dbPathLabel = "operators.operatorframework.io.index.database.v1" + alphaDisplayNameLabel = "alpha.operators.operatorframework.io.index.display-name.v1" + alphaPublisherLabel = "alpha.operators.operatorframework.io.index.publisher.v1" + defaultDatabasePath = "/database/index.db" +) type CatalogAdd struct { config *Configuration @@ -81,7 +94,7 @@ func (a *CatalogAdd) Run(ctx context.Context) (*v1alpha1.CatalogSource, error) { labels, err := a.labelsFor(ctx, a.IndexImage) if err != nil { - return nil, err + return nil, fmt.Errorf("get image labels: %v", err) } a.setDefaults(labels) @@ -96,20 +109,33 @@ func (a *CatalogAdd) Run(ctx context.Context) (*v1alpha1.CatalogSource, error) { } cs := catalog.Build(csKey, opts...) - if err := a.createCatalogSource(ctx, cs); err != nil { - return nil, err + if err := a.config.Client.Create(ctx, cs); err != nil { + return nil, fmt.Errorf("create catalogsource: %v", err) } var registryPod *corev1.Pod if len(a.InjectBundles) > 0 { - if registryPod, err = a.createRegistryPod(ctx, cs); err != nil { + dbPath, ok := labels[dbPathLabel] + if !ok { + // No database path label, so assume this is an index base image. + // Choose "semver" bundle add mode (if not explicitly set) and + // use the default database path. + if a.InjectBundleMode == "" { + a.InjectBundleMode = "semver" + } + dbPath = defaultDatabasePath + } + if a.InjectBundleMode == "" { + a.InjectBundleMode = "replaces" + } + if registryPod, err = a.createRegistryPod(ctx, cs, dbPath); err != nil { defer a.cleanup(cs) return nil, err } if err := a.updateCatalogSource(ctx, cs, registryPod); err != nil { defer a.cleanup(cs) - return nil, err + return nil, fmt.Errorf("update catalog source: %v", err) } } @@ -122,51 +148,61 @@ func (a *CatalogAdd) Run(ctx context.Context) (*v1alpha1.CatalogSource, error) { } func (a *CatalogAdd) labelsFor(ctx context.Context, indexImage string) (map[string]string, error) { - simpleRef := image.SimpleReference(indexImage) - if err := a.registry.Pull(ctx, simpleRef); err != nil { + ref := image.SimpleReference(indexImage) + if err := a.registry.Pull(ctx, ref); err != nil { return nil, fmt.Errorf("pull image: %v", err) } - labels, err := a.registry.Labels(ctx, simpleRef) + + ctx = namespaces.WithNamespace(ctx, namespaces.Default) + img, err := a.registry.Images().Get(ctx, ref.String()) if err != nil { - return nil, fmt.Errorf("get image labels: %v", err) + return nil, fmt.Errorf("get image from local registry: %v", err) } - return labels, nil + + manifest, err := images.Manifest(ctx, a.registry.Content(), img.Target, platforms.All) + if err != nil { + return nil, fmt.Errorf("resolve image manifest: %v", err) + } + + ra, err := a.registry.Content().ReaderAt(ctx, manifest.Config) + if err != nil { + return nil, fmt.Errorf("get image reader: %v", err) + } + defer ra.Close() + + decompressed, err := compression.DecompressStream(io.NewSectionReader(ra, 0, ra.Size())) + if err != nil { + return nil, fmt.Errorf("decompress image data: %v", err) + } + var imageMeta ocispec.Image + dec := json.NewDecoder(decompressed) + if err := dec.Decode(&imageMeta); err != nil { + return nil, fmt.Errorf("decode image metadata: %v", err) + } + return imageMeta.Config.Labels, nil } func (a *CatalogAdd) setDefaults(labels map[string]string) { if a.DisplayName == "" { - if v, ok := labels["operators.operatorframework.io.index.display-name"]; ok { + if v, ok := labels[alphaDisplayNameLabel]; ok { a.DisplayName = v } } if a.Publisher == "" { - if v, ok := labels["operators.operatorframework.io.index.publisher"]; ok { + if v, ok := labels[alphaPublisherLabel]; ok { a.Publisher = v } } - if a.InjectBundleMode == "" { - if strings.HasPrefix(a.IndexImage, "quay.io/operator-framework/upstream-opm-builder") { - a.InjectBundleMode = "semver" - } else { - a.InjectBundleMode = "replaces" - } - } -} - -func (a *CatalogAdd) createCatalogSource(ctx context.Context, cs *v1alpha1.CatalogSource) error { - if err := a.config.Client.Create(ctx, cs); err != nil { - return fmt.Errorf("create catalogsource: %v", err) - } - return nil } -func (a *CatalogAdd) createRegistryPod(ctx context.Context, cs *v1alpha1.CatalogSource) (*corev1.Pod, error) { +func (a *CatalogAdd) createRegistryPod(ctx context.Context, cs *v1alpha1.CatalogSource, dbPath string) (*corev1.Pod, error) { + dbDir := path.Dir(dbPath) command := []string{ "/bin/sh", "-c", - fmt.Sprintf(`mkdir -p /database && \ -/bin/opm registry add -d /database/index.db --mode=%s -b %s && \ -/bin/opm registry serve -d /database/index.db -p %s`, a.InjectBundleMode, strings.Join(a.InjectBundles, ","), grpcPort), + fmt.Sprintf(`mkdir -p %s && \ +/bin/opm registry add -d %s --mode=%s -b %s && \ +/bin/opm registry serve -d %s -p %s`, dbDir, dbPath, a.InjectBundleMode, strings.Join(a.InjectBundles, ","), dbPath, grpcPort), } pod := &corev1.Pod{ @@ -184,26 +220,15 @@ func (a *CatalogAdd) createRegistryPod(ctx context.Context, cs *v1alpha1.Catalog }, }, } + + if err := controllerutil.SetOwnerReference(cs, pod, a.config.Scheme); err != nil { + return nil, fmt.Errorf("set registry pod owner reference: %v", err) + } if err := a.config.Client.Create(ctx, pod); err != nil { return nil, fmt.Errorf("create registry pod: %v", err) } podKey := objectKeyForObject(pod) - if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { - if err := a.config.Client.Get(ctx, podKey, pod); err != nil { - return fmt.Errorf("get registry pod: %v", err) - } - if err := controllerutil.SetOwnerReference(cs, pod, a.config.Scheme); err != nil { - return fmt.Errorf("set registry pod owner reference: %v", err) - } - if err := a.config.Client.Update(ctx, pod); err != nil { - return fmt.Errorf("update registry pod owner reference: %v", err) - } - return nil - }); err != nil { - return nil, err - } - if err := wait.PollImmediateUntil(time.Millisecond*250, func() (bool, error) { if err := a.config.Client.Get(ctx, podKey, pod); err != nil { return false, err @@ -219,26 +244,25 @@ func (a *CatalogAdd) createRegistryPod(ctx context.Context, cs *v1alpha1.Catalog } func (a *CatalogAdd) updateCatalogSource(ctx context.Context, cs *v1alpha1.CatalogSource, pod *corev1.Pod) error { - cs.Spec.Address = fmt.Sprintf("%s:%s", pod.Status.PodIP, grpcPort) - injectedBundlesJSON, err := json.Marshal(a.InjectBundles) if err != nil { return fmt.Errorf("json marshal injected bundles: %v", err) } - cs.ObjectMeta.Annotations = map[string]string{ - "operators.operatorframework.io/index-image": a.IndexImage, - "operators.operatorframework.io/inject-bundle-mode": a.InjectBundleMode, - "operators.operatorframework.io/injected-bundles": string(injectedBundlesJSON), - } + csKey := objectKeyForObject(cs) if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { if err := a.config.Client.Get(ctx, csKey, cs); err != nil { return fmt.Errorf("get catalog source: %v", err) } - if err := a.config.Client.Update(ctx, cs); err != nil { - return fmt.Errorf("update catalog source: %v", err) + + cs.Spec.Address = fmt.Sprintf("%s:%s", pod.Status.PodIP, grpcPort) + cs.ObjectMeta.Annotations = map[string]string{ + "operators.operatorframework.io/index-image": a.IndexImage, + "operators.operatorframework.io/inject-bundle-mode": a.InjectBundleMode, + "operators.operatorframework.io/injected-bundles": string(injectedBundlesJSON), } - return nil + + return a.config.Client.Update(ctx, cs) }); err != nil { return err }