Skip to content
149 changes: 149 additions & 0 deletions internal/olm/operator/internal/configmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright 2019 The Operator-SDK Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package olm

import (
Copy link
Contributor

@camilamacedo86 camilamacedo86 Dec 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the path is internal/olm/operator/internal again?
Could not it be just internal/olm/registry since what all it is doing is creating the Registry?
Also, it if is a public API then, I understand that, should be in the pkg instead of internals.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be internal/olm/registry, but this package is only intended to be used by code in internal/olm/operator, hence its current path. If that changes in the future we can move it. I recommend going through #1912 to get a handle on why this code is set up as-is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. But it shows strange internal/.../internal again.
WDYT about internal/olm/operator/registry

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't a common use case but it is a completely viable use of internal.

"context"
"crypto/md5"
"encoding/base32"
"fmt"
"strings"

"github.com/operator-framework/operator-sdk/internal/util/k8sutil"

"github.com/ghodss/yaml"
"github.com/operator-framework/operator-registry/pkg/registry"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)

const (
// The directory containing all manifests for an operator, with the
// package manifest being top-level.
containerManifestsDir = "/registry/manifests"
)

// IsManifestDataStale checks if manifest data stored in the registry is stale
// by comparing it to manifest data currently managed by m.
func (m *RegistryResources) IsManifestDataStale(ctx context.Context, namespace string) (bool, error) {
pkgName := m.Pkg.PackageName
nn := types.NamespacedName{
Name: getRegistryConfigMapName(pkgName),
Namespace: namespace,
}
configmap := corev1.ConfigMap{}
err := m.Client.KubeClient.Get(ctx, nn, &configmap)
if err != nil {
return false, err
}
// Collect digests of manifests submitted to m.
newData, err := createConfigMapBinaryData(m.Pkg, m.Bundles)
if err != nil {
return false, fmt.Errorf("error creating binary data: %w", err)
}
// If the number of files to be added to the registry don't match the number
// of files currently in the registry, we have added or removed a file.
if len(newData) != len(configmap.BinaryData) {
return true, nil
}
// Check each binary value's key, which contains a base32-encoded md5 digest
// component, against the new set of manifest keys.
for fileKey := range configmap.BinaryData {
if _, match := newData[fileKey]; !match {
return true, nil
}
}
return false, nil
}

// hashContents creates a base32-encoded md5 digest of b's bytes.
func hashContents(b []byte) string {
h := md5.New()
_, _ = h.Write(b)
enc := base32.StdEncoding.WithPadding(base32.NoPadding)
return enc.EncodeToString(h.Sum(nil))
}

// getObjectFileName opaquely creates a unique file name based on data in b.
func getObjectFileName(b []byte, name, kind string) string {
digest := hashContents(b)
return fmt.Sprintf("%s.%s.%s.yaml", digest, name, strings.ToLower(kind))
}

func getPackageFileName(b []byte, name string) string {
return getObjectFileName(b, name, "package")
}

// createConfigMapBinaryData opaquely creates a set of paths using data in pkg
// and each bundle in bundles, unique by path. These paths are intended to
// be keys in a ConfigMap.
func createConfigMapBinaryData(pkg registry.PackageManifest, bundles []*registry.Bundle) (map[string][]byte, error) {
pkgName := pkg.PackageName
binaryKeyValues := map[string][]byte{}
pb, err := yaml.Marshal(pkg)
if err != nil {
return nil, fmt.Errorf("error marshalling package manifest %s: %w", pkgName, err)
}
binaryKeyValues[getPackageFileName(pb, pkgName)] = pb
for _, bundle := range bundles {
for _, o := range bundle.Objects {
ob, err := yaml.Marshal(o)
if err != nil {
return nil, fmt.Errorf("error marshalling object %s %q: %w", o.GroupVersionKind(), o.GetName(), err)
}
binaryKeyValues[getObjectFileName(ob, o.GetName(), o.GetKind())] = ob
}
}
return binaryKeyValues, nil
}

func getRegistryConfigMapName(pkgName string) string {
name := k8sutil.FormatOperatorNameDNS1123(pkgName)
return fmt.Sprintf("%s-registry-bundles", name)
}

// withBinaryData returns a function that creates entries in the ConfigMap
// argument's binaryData for each key and []byte value in kvs.
func withBinaryData(kvs map[string][]byte) func(*corev1.ConfigMap) {
return func(cm *corev1.ConfigMap) {
if cm.BinaryData == nil {
cm.BinaryData = map[string][]byte{}
}
for k, v := range kvs {
cm.BinaryData[k] = v
}
}
}

// newRegistryConfigMap creates a new ConfigMap with a name derived from
// pkgName, the package manifest's packageName, in namespace. opts will
// be applied to the ConfigMap object.
func newRegistryConfigMap(pkgName, namespace string, opts ...func(*corev1.ConfigMap)) *corev1.ConfigMap {
cm := &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: getRegistryConfigMapName(pkgName),
Namespace: namespace,
},
}
for _, opt := range opts {
opt(cm)
}
return cm
}
176 changes: 176 additions & 0 deletions internal/olm/operator/internal/deployment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright 2019 The Operator-SDK Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package olm

import (
"fmt"

"github.com/operator-framework/operator-sdk/internal/util/k8sutil"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
// The image operator-registry's initializer and registry-server binaries
// are run from.
// QUESTION(estroz): version registry image?
registryBaseImage = "quay.io/openshift/origin-operator-registry:latest"
// The port registry-server will listen on within a container.
registryGRPCPort = 50051
// Path of the bundle database generated by initializer.
regisryDBName = "bundle.db"
// Path of the log file generated by registry-server.
// TODO(estroz): have this log file in an obvious place, ex. /var/log.
registryLogFile = "termination.log"
)

func getRegistryServerName(pkgName string) string {
name := k8sutil.FormatOperatorNameDNS1123(pkgName)
return fmt.Sprintf("%s-registry-server", name)
}

func getRegistryVolumeName(pkgName string) string {
name := k8sutil.FormatOperatorNameDNS1123(pkgName)
return fmt.Sprintf("%s-bundle-db", name)
}

// getRegistryDeploymentLabels creates a set of labels to identify
// operator-registry Deployment objects.
func getRegistryDeploymentLabels(pkgName string) map[string]string {
labels := map[string]string{
"name": getRegistryServerName(pkgName),
}
for k, v := range SDKLabels {
labels[k] = v
}
return labels
}

// applyToDeploymentPodSpec applies f to dep's pod template spec.
func applyToDeploymentPodSpec(dep *appsv1.Deployment, f func(*corev1.PodSpec)) {
f(&dep.Spec.Template.Spec)
}

// withVolumeConfigMap returns a function that appends a volume with name
// volName containing a reference to a ConfigMap with name cmName to the
// Deployment argument's pod template spec.
func withVolumeConfigMap(volName, cmName string) func(*appsv1.Deployment) {
volume := corev1.Volume{
Name: volName,
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: cmName,
},
},
},
}
return func(dep *appsv1.Deployment) {
applyToDeploymentPodSpec(dep, func(spec *corev1.PodSpec) {
spec.Volumes = append(spec.Volumes, volume)
})
}
}

// withContainerVolumeMounts returns a function that appends volumeMounts
// to each container in the Deployment argument's pod template spec. One
// volumeMount is appended for each path in paths from volume with name
// volName.
func withContainerVolumeMounts(volName string, paths []string) func(*appsv1.Deployment) {
volumeMounts := []corev1.VolumeMount{}
for _, p := range paths {
volumeMounts = append(volumeMounts, corev1.VolumeMount{
Name: volName,
MountPath: p,
})
}
return func(dep *appsv1.Deployment) {
applyToDeploymentPodSpec(dep, func(spec *corev1.PodSpec) {
for i := range spec.Containers {
spec.Containers[i].VolumeMounts = append(spec.Containers[i].VolumeMounts, volumeMounts...)
}
})
}
}

// getDBContainerCmd returns a command string that, when run, does two things:
// 1. Runs a database initializer on the manifests in the current working
// directory.
// 2. Runs an operator-registry server serving the bundle database.
// The database must be in the current working directory.
func getDBContainerCmd(dbPath, logPath string) string {
initCmd := fmt.Sprintf("/usr/bin/initializer -o %s", dbPath)
srvCmd := fmt.Sprintf("/usr/bin/registry-server -d %s -t %s", dbPath, logPath)
return fmt.Sprintf("%s && %s", initCmd, srvCmd)
}

// withRegistryGRPCContainer returns a function that appends a container
// running an operator-registry GRPC server to the Deployment argument's
// pod template spec.
func withRegistryGRPCContainer(pkgName string) func(*appsv1.Deployment) {
container := corev1.Container{
Name: getRegistryServerName(pkgName),
Image: registryBaseImage,
Command: []string{"/bin/bash"},
Args: []string{
"-c",
// TODO(estroz): grab logs and print if error
getDBContainerCmd(regisryDBName, registryLogFile),
},
Ports: []corev1.ContainerPort{
{Name: "registry-grpc", ContainerPort: registryGRPCPort},
},
}
return func(dep *appsv1.Deployment) {
applyToDeploymentPodSpec(dep, func(spec *corev1.PodSpec) {
spec.Containers = append(spec.Containers, container)
})
}
}

// newRegistryDeployment creates a new Deployment with a name derived from
// pkgName, the package manifest's packageName, in namespace. The Deployment
// and replicas are created with labels derived from pkgName. opts will be
// applied to the Deployment object.
func newRegistryDeployment(pkgName, namespace string, opts ...func(*appsv1.Deployment)) *appsv1.Deployment {
var replicas int32 = 1
dep := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: appsv1.SchemeGroupVersion.String(),
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Name: getRegistryServerName(pkgName),
Namespace: namespace,
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: getRegistryDeploymentLabels(pkgName),
},
Replicas: &replicas,
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: getRegistryDeploymentLabels(pkgName),
},
},
},
}
for _, opt := range opts {
opt(dep)
}
return dep
}
Loading