Skip to content

Commit 93f23d7

Browse files
author
Eric Stroczynski
authored
internal/olm/operator/internal: operator-registry wrappers (#2313)
* internal/olm/operator/internal: tools to create and delete a manifest registry * test/test-framework: revendor
1 parent 872b051 commit 93f23d7

File tree

5 files changed

+492
-0
lines changed

5 files changed

+492
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Copyright 2019 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package olm
16+
17+
import (
18+
"context"
19+
"crypto/md5"
20+
"encoding/base32"
21+
"fmt"
22+
"strings"
23+
24+
"github.com/operator-framework/operator-sdk/internal/util/k8sutil"
25+
26+
"github.com/ghodss/yaml"
27+
"github.com/operator-framework/operator-registry/pkg/registry"
28+
corev1 "k8s.io/api/core/v1"
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/apimachinery/pkg/types"
31+
)
32+
33+
const (
34+
// The directory containing all manifests for an operator, with the
35+
// package manifest being top-level.
36+
containerManifestsDir = "/registry/manifests"
37+
)
38+
39+
// IsManifestDataStale checks if manifest data stored in the registry is stale
40+
// by comparing it to manifest data currently managed by m.
41+
func (m *RegistryResources) IsManifestDataStale(ctx context.Context, namespace string) (bool, error) {
42+
pkgName := m.Pkg.PackageName
43+
nn := types.NamespacedName{
44+
Name: getRegistryConfigMapName(pkgName),
45+
Namespace: namespace,
46+
}
47+
configmap := corev1.ConfigMap{}
48+
err := m.Client.KubeClient.Get(ctx, nn, &configmap)
49+
if err != nil {
50+
return false, err
51+
}
52+
// Collect digests of manifests submitted to m.
53+
newData, err := createConfigMapBinaryData(m.Pkg, m.Bundles)
54+
if err != nil {
55+
return false, fmt.Errorf("error creating binary data: %w", err)
56+
}
57+
// If the number of files to be added to the registry don't match the number
58+
// of files currently in the registry, we have added or removed a file.
59+
if len(newData) != len(configmap.BinaryData) {
60+
return true, nil
61+
}
62+
// Check each binary value's key, which contains a base32-encoded md5 digest
63+
// component, against the new set of manifest keys.
64+
for fileKey := range configmap.BinaryData {
65+
if _, match := newData[fileKey]; !match {
66+
return true, nil
67+
}
68+
}
69+
return false, nil
70+
}
71+
72+
// hashContents creates a base32-encoded md5 digest of b's bytes.
73+
func hashContents(b []byte) string {
74+
h := md5.New()
75+
_, _ = h.Write(b)
76+
enc := base32.StdEncoding.WithPadding(base32.NoPadding)
77+
return enc.EncodeToString(h.Sum(nil))
78+
}
79+
80+
// getObjectFileName opaquely creates a unique file name based on data in b.
81+
func getObjectFileName(b []byte, name, kind string) string {
82+
digest := hashContents(b)
83+
return fmt.Sprintf("%s.%s.%s.yaml", digest, name, strings.ToLower(kind))
84+
}
85+
86+
func getPackageFileName(b []byte, name string) string {
87+
return getObjectFileName(b, name, "package")
88+
}
89+
90+
// createConfigMapBinaryData opaquely creates a set of paths using data in pkg
91+
// and each bundle in bundles, unique by path. These paths are intended to
92+
// be keys in a ConfigMap.
93+
func createConfigMapBinaryData(pkg registry.PackageManifest, bundles []*registry.Bundle) (map[string][]byte, error) {
94+
pkgName := pkg.PackageName
95+
binaryKeyValues := map[string][]byte{}
96+
pb, err := yaml.Marshal(pkg)
97+
if err != nil {
98+
return nil, fmt.Errorf("error marshalling package manifest %s: %w", pkgName, err)
99+
}
100+
binaryKeyValues[getPackageFileName(pb, pkgName)] = pb
101+
for _, bundle := range bundles {
102+
for _, o := range bundle.Objects {
103+
ob, err := yaml.Marshal(o)
104+
if err != nil {
105+
return nil, fmt.Errorf("error marshalling object %s %q: %w", o.GroupVersionKind(), o.GetName(), err)
106+
}
107+
binaryKeyValues[getObjectFileName(ob, o.GetName(), o.GetKind())] = ob
108+
}
109+
}
110+
return binaryKeyValues, nil
111+
}
112+
113+
func getRegistryConfigMapName(pkgName string) string {
114+
name := k8sutil.FormatOperatorNameDNS1123(pkgName)
115+
return fmt.Sprintf("%s-registry-bundles", name)
116+
}
117+
118+
// withBinaryData returns a function that creates entries in the ConfigMap
119+
// argument's binaryData for each key and []byte value in kvs.
120+
func withBinaryData(kvs map[string][]byte) func(*corev1.ConfigMap) {
121+
return func(cm *corev1.ConfigMap) {
122+
if cm.BinaryData == nil {
123+
cm.BinaryData = map[string][]byte{}
124+
}
125+
for k, v := range kvs {
126+
cm.BinaryData[k] = v
127+
}
128+
}
129+
}
130+
131+
// newRegistryConfigMap creates a new ConfigMap with a name derived from
132+
// pkgName, the package manifest's packageName, in namespace. opts will
133+
// be applied to the ConfigMap object.
134+
func newRegistryConfigMap(pkgName, namespace string, opts ...func(*corev1.ConfigMap)) *corev1.ConfigMap {
135+
cm := &corev1.ConfigMap{
136+
TypeMeta: metav1.TypeMeta{
137+
APIVersion: corev1.SchemeGroupVersion.String(),
138+
Kind: "ConfigMap",
139+
},
140+
ObjectMeta: metav1.ObjectMeta{
141+
Name: getRegistryConfigMapName(pkgName),
142+
Namespace: namespace,
143+
},
144+
}
145+
for _, opt := range opts {
146+
opt(cm)
147+
}
148+
return cm
149+
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Copyright 2019 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package olm
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/operator-framework/operator-sdk/internal/util/k8sutil"
21+
22+
appsv1 "k8s.io/api/apps/v1"
23+
corev1 "k8s.io/api/core/v1"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
)
26+
27+
const (
28+
// The image operator-registry's initializer and registry-server binaries
29+
// are run from.
30+
// QUESTION(estroz): version registry image?
31+
registryBaseImage = "quay.io/openshift/origin-operator-registry:latest"
32+
// The port registry-server will listen on within a container.
33+
registryGRPCPort = 50051
34+
// Path of the bundle database generated by initializer.
35+
regisryDBName = "bundle.db"
36+
// Path of the log file generated by registry-server.
37+
// TODO(estroz): have this log file in an obvious place, ex. /var/log.
38+
registryLogFile = "termination.log"
39+
)
40+
41+
func getRegistryServerName(pkgName string) string {
42+
name := k8sutil.FormatOperatorNameDNS1123(pkgName)
43+
return fmt.Sprintf("%s-registry-server", name)
44+
}
45+
46+
func getRegistryVolumeName(pkgName string) string {
47+
name := k8sutil.FormatOperatorNameDNS1123(pkgName)
48+
return fmt.Sprintf("%s-bundle-db", name)
49+
}
50+
51+
// getRegistryDeploymentLabels creates a set of labels to identify
52+
// operator-registry Deployment objects.
53+
func getRegistryDeploymentLabels(pkgName string) map[string]string {
54+
labels := map[string]string{
55+
"name": getRegistryServerName(pkgName),
56+
}
57+
for k, v := range SDKLabels {
58+
labels[k] = v
59+
}
60+
return labels
61+
}
62+
63+
// applyToDeploymentPodSpec applies f to dep's pod template spec.
64+
func applyToDeploymentPodSpec(dep *appsv1.Deployment, f func(*corev1.PodSpec)) {
65+
f(&dep.Spec.Template.Spec)
66+
}
67+
68+
// withVolumeConfigMap returns a function that appends a volume with name
69+
// volName containing a reference to a ConfigMap with name cmName to the
70+
// Deployment argument's pod template spec.
71+
func withVolumeConfigMap(volName, cmName string) func(*appsv1.Deployment) {
72+
volume := corev1.Volume{
73+
Name: volName,
74+
VolumeSource: corev1.VolumeSource{
75+
ConfigMap: &corev1.ConfigMapVolumeSource{
76+
LocalObjectReference: corev1.LocalObjectReference{
77+
Name: cmName,
78+
},
79+
},
80+
},
81+
}
82+
return func(dep *appsv1.Deployment) {
83+
applyToDeploymentPodSpec(dep, func(spec *corev1.PodSpec) {
84+
spec.Volumes = append(spec.Volumes, volume)
85+
})
86+
}
87+
}
88+
89+
// withContainerVolumeMounts returns a function that appends volumeMounts
90+
// to each container in the Deployment argument's pod template spec. One
91+
// volumeMount is appended for each path in paths from volume with name
92+
// volName.
93+
func withContainerVolumeMounts(volName string, paths []string) func(*appsv1.Deployment) {
94+
volumeMounts := []corev1.VolumeMount{}
95+
for _, p := range paths {
96+
volumeMounts = append(volumeMounts, corev1.VolumeMount{
97+
Name: volName,
98+
MountPath: p,
99+
})
100+
}
101+
return func(dep *appsv1.Deployment) {
102+
applyToDeploymentPodSpec(dep, func(spec *corev1.PodSpec) {
103+
for i := range spec.Containers {
104+
spec.Containers[i].VolumeMounts = append(spec.Containers[i].VolumeMounts, volumeMounts...)
105+
}
106+
})
107+
}
108+
}
109+
110+
// getDBContainerCmd returns a command string that, when run, does two things:
111+
// 1. Runs a database initializer on the manifests in the current working
112+
// directory.
113+
// 2. Runs an operator-registry server serving the bundle database.
114+
// The database must be in the current working directory.
115+
func getDBContainerCmd(dbPath, logPath string) string {
116+
initCmd := fmt.Sprintf("/usr/bin/initializer -o %s", dbPath)
117+
srvCmd := fmt.Sprintf("/usr/bin/registry-server -d %s -t %s", dbPath, logPath)
118+
return fmt.Sprintf("%s && %s", initCmd, srvCmd)
119+
}
120+
121+
// withRegistryGRPCContainer returns a function that appends a container
122+
// running an operator-registry GRPC server to the Deployment argument's
123+
// pod template spec.
124+
func withRegistryGRPCContainer(pkgName string) func(*appsv1.Deployment) {
125+
container := corev1.Container{
126+
Name: getRegistryServerName(pkgName),
127+
Image: registryBaseImage,
128+
Command: []string{"/bin/bash"},
129+
Args: []string{
130+
"-c",
131+
// TODO(estroz): grab logs and print if error
132+
getDBContainerCmd(regisryDBName, registryLogFile),
133+
},
134+
Ports: []corev1.ContainerPort{
135+
{Name: "registry-grpc", ContainerPort: registryGRPCPort},
136+
},
137+
}
138+
return func(dep *appsv1.Deployment) {
139+
applyToDeploymentPodSpec(dep, func(spec *corev1.PodSpec) {
140+
spec.Containers = append(spec.Containers, container)
141+
})
142+
}
143+
}
144+
145+
// newRegistryDeployment creates a new Deployment with a name derived from
146+
// pkgName, the package manifest's packageName, in namespace. The Deployment
147+
// and replicas are created with labels derived from pkgName. opts will be
148+
// applied to the Deployment object.
149+
func newRegistryDeployment(pkgName, namespace string, opts ...func(*appsv1.Deployment)) *appsv1.Deployment {
150+
var replicas int32 = 1
151+
dep := &appsv1.Deployment{
152+
TypeMeta: metav1.TypeMeta{
153+
APIVersion: appsv1.SchemeGroupVersion.String(),
154+
Kind: "Deployment",
155+
},
156+
ObjectMeta: metav1.ObjectMeta{
157+
Name: getRegistryServerName(pkgName),
158+
Namespace: namespace,
159+
},
160+
Spec: appsv1.DeploymentSpec{
161+
Selector: &metav1.LabelSelector{
162+
MatchLabels: getRegistryDeploymentLabels(pkgName),
163+
},
164+
Replicas: &replicas,
165+
Template: corev1.PodTemplateSpec{
166+
ObjectMeta: metav1.ObjectMeta{
167+
Labels: getRegistryDeploymentLabels(pkgName),
168+
},
169+
},
170+
},
171+
}
172+
for _, opt := range opts {
173+
opt(dep)
174+
}
175+
return dep
176+
}

0 commit comments

Comments
 (0)