Skip to content

Commit

Permalink
internal/pkg/action/catalog_add.go: several improvements (#21)
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
joelanford authored Sep 3, 2020
1 parent 1f47404 commit b333571
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 56 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
136 changes: 80 additions & 56 deletions internal/pkg/action/catalog_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
}

Expand All @@ -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{
Expand All @@ -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
Expand All @@ -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
}
Expand Down

0 comments on commit b333571

Please sign in to comment.