Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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) {
Comment thread
estroz marked this conversation as resolved.
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