Skip to content

Commit 85cc57f

Browse files
committed
Add MagicCatalog for creating arbitrary file-based catalogs
Signed-off-by: Per G. da Silva <[email protected]>
1 parent cabe200 commit 85cc57f

File tree

6 files changed

+393
-2
lines changed

6 files changed

+393
-2
lines changed

Diff for: test/e2e/csv_e2e_test.go

-2
Original file line numberDiff line numberDiff line change
@@ -4423,8 +4423,6 @@ var _ = Describe("Disabling copied CSVs", func() {
44234423

44244424
var singleInstance = int32(1)
44254425

4426-
type cleanupFunc func()
4427-
44284426
var immediateDeleteGracePeriod int64 = 0
44294427

44304428
func findLastEvent(events *corev1.EventList) (event corev1.Event) {

Diff for: test/e2e/fbc_provider.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package e2e
2+
3+
import (
4+
"io/ioutil"
5+
)
6+
7+
type FileBasedCatalogProvider interface {
8+
GetCatalog() string
9+
}
10+
11+
type fileBasedFileBasedCatalogProvider struct {
12+
fbc string
13+
}
14+
15+
func NewFileBasedFiledBasedCatalogProvider(path string) (FileBasedCatalogProvider, error) {
16+
data, err := ioutil.ReadFile(path)
17+
if err != nil {
18+
return nil, err
19+
}
20+
21+
return &fileBasedFileBasedCatalogProvider{
22+
fbc: string(data),
23+
}, nil
24+
}
25+
26+
func (f *fileBasedFileBasedCatalogProvider) GetCatalog() string {
27+
return f.fbc
28+
}

Diff for: test/e2e/magic_catalog.go

+279
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
package e2e
2+
3+
import (
4+
"context"
5+
"fmt"
6+
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
7+
corev1 "k8s.io/api/core/v1"
8+
k8serror "k8s.io/apimachinery/pkg/api/errors"
9+
"k8s.io/apimachinery/pkg/api/resource"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/util/intstr"
12+
k8scontrollerclient "sigs.k8s.io/controller-runtime/pkg/client"
13+
)
14+
15+
const (
16+
olmCatalogLabel string = "olm.catalogSource"
17+
catalogMountPath string = "/opt/olm"
18+
catalogServicePort int32 = 50051
19+
catalogReadyState string = "READY"
20+
)
21+
22+
type MagicCatalog interface {
23+
DeployCatalog(ctx context.Context) error
24+
UndeployCatalog(ctx context.Context) []error
25+
}
26+
27+
type magicCatalog struct {
28+
fileBasedCatalog FileBasedCatalogProvider
29+
kubeClient k8scontrollerclient.Client
30+
namespace string
31+
name string
32+
configMapName string
33+
serviceName string
34+
podName string
35+
}
36+
37+
// NewMagicCatalog creates an object that can deploy an arbitrary file-based catalog given by the FileBasedCatalogProvider
38+
// Keep in mind that there are limits to the configMaps. So, the catalogs need to be relatively simple
39+
func NewMagicCatalog(kubeClient k8scontrollerclient.Client, namespace string, catalogName string, provider FileBasedCatalogProvider) MagicCatalog {
40+
return &magicCatalog{
41+
fileBasedCatalog: provider,
42+
kubeClient: kubeClient,
43+
namespace: namespace,
44+
name: catalogName,
45+
configMapName: catalogName + "-configmap",
46+
serviceName: catalogName + "-svc",
47+
podName: catalogName + "-pod",
48+
}
49+
}
50+
51+
func (c *magicCatalog) DeployCatalog(ctx context.Context) error {
52+
53+
catalogSource := c.makeCatalogSource()
54+
resourcesInOrderOfDeployment := []k8scontrollerclient.Object{
55+
c.makeConfigMap(),
56+
c.makeCatalogSourcePod(),
57+
c.makeCatalogService(),
58+
catalogSource,
59+
}
60+
61+
for _, res := range resourcesInOrderOfDeployment {
62+
err := c.kubeClient.Create(ctx, res)
63+
if err != nil {
64+
return c.cleanUpAfter(ctx, err)
65+
}
66+
}
67+
68+
// wait for catalog source to become ready
69+
err := waitFor(func() (bool, error) {
70+
err := c.kubeClient.Get(ctx, k8scontrollerclient.ObjectKey{
71+
Name: catalogSource.GetName(),
72+
Namespace: catalogSource.GetNamespace(),
73+
}, catalogSource)
74+
75+
if err != nil {
76+
return false, err
77+
}
78+
79+
state := catalogSource.Status.GRPCConnectionState.LastObservedState
80+
81+
if state != catalogReadyState {
82+
return false, nil
83+
} else {
84+
return true, nil
85+
}
86+
})
87+
88+
if err != nil {
89+
return c.cleanUpAfter(ctx, err)
90+
}
91+
92+
return nil
93+
}
94+
95+
func (c *magicCatalog) UndeployCatalog(ctx context.Context) []error {
96+
var errs []error = nil
97+
98+
resourcesInOrderOfDeletion := []k8scontrollerclient.Object{
99+
c.makeCatalogSource(),
100+
c.makeCatalogService(),
101+
c.makeCatalogSourcePod(),
102+
c.makeConfigMap(),
103+
}
104+
105+
// try to delete all resourcesInOrderOfDeletion even if errors are
106+
// encountered through deletion.
107+
for _, res := range resourcesInOrderOfDeletion {
108+
err := c.kubeClient.Delete(ctx, res)
109+
110+
// ignore not found errors
111+
if err != nil && !k8serror.IsNotFound(err) {
112+
if errs == nil {
113+
errs = make([]error, 0)
114+
}
115+
errs = append(errs, err)
116+
}
117+
}
118+
119+
return errs
120+
}
121+
122+
func (c *magicCatalog) cleanUpAfter(ctx context.Context, err error) error {
123+
cleanupErr := c.UndeployCatalog(ctx)
124+
if cleanupErr != nil {
125+
return fmt.Errorf("the following cleanup errors occurred: '%s' after an error deploying the configmap: '%s' ", cleanupErr, err)
126+
}
127+
return err
128+
}
129+
130+
func (c *magicCatalog) makeCatalogService() *corev1.Service {
131+
return &corev1.Service{
132+
ObjectMeta: metav1.ObjectMeta{
133+
Name: c.serviceName,
134+
Namespace: c.namespace,
135+
},
136+
Spec: corev1.ServiceSpec{
137+
Ports: []corev1.ServicePort{
138+
{
139+
Name: "grpc",
140+
Port: catalogServicePort,
141+
Protocol: "TCP",
142+
TargetPort: intstr.FromInt(int(catalogServicePort)),
143+
},
144+
},
145+
Selector: c.makeCatalogSourcePodLabels(),
146+
},
147+
}
148+
}
149+
150+
func (c *magicCatalog) makeConfigMap() *corev1.ConfigMap {
151+
isImmutable := true
152+
return &corev1.ConfigMap{
153+
ObjectMeta: metav1.ObjectMeta{
154+
Name: c.configMapName,
155+
Namespace: c.namespace,
156+
},
157+
Immutable: &isImmutable,
158+
Data: map[string]string{
159+
"catalog.json": c.fileBasedCatalog.GetCatalog(),
160+
// due to the way files get mounted to pods from configMaps
161+
// it is important to add _this_ .indexignore
162+
//
163+
// The mount folder will look something like this:
164+
// /opt/olm
165+
// |--> ..2021_12_15_02_01_11.729011450
166+
// |--> catalog.json
167+
// |--> .indexignore
168+
// |--> ..data -> ..2021_12_15_02_01_11.729011450
169+
// |--> catalog.json -> ..data/catalog.json
170+
// |--> .indexignore -> ..data/.indexignore
171+
// Adding '**/..*' to the .indexignore ensures the
172+
// '..2021_12_15_02_01_11.729011450' and ' ..data' directories are ignored.
173+
// Otherwise, opm will pick up on both catalog.json files and fail with a conflicts (duplicate packages)
174+
".indexignore": "**/\\.\\.*\n",
175+
},
176+
}
177+
}
178+
179+
func (c *magicCatalog) makeCatalogSource() *operatorsv1alpha1.CatalogSource {
180+
return &operatorsv1alpha1.CatalogSource{
181+
ObjectMeta: metav1.ObjectMeta{
182+
Name: c.name,
183+
Namespace: c.namespace,
184+
},
185+
Spec: operatorsv1alpha1.CatalogSourceSpec{
186+
SourceType: operatorsv1alpha1.SourceTypeGrpc,
187+
Address: fmt.Sprintf("%s.%s.svc:50051", c.serviceName, c.namespace),
188+
},
189+
}
190+
}
191+
192+
func (c *magicCatalog) makeCatalogSourcePod() *corev1.Pod {
193+
194+
const (
195+
image = "quay.io/operator-framework/upstream-opm-builder"
196+
readinessDelay int32 = 5
197+
livenessDelay int32 = 10
198+
volumeMountName string = "fbc-catalog"
199+
)
200+
201+
readOnlyRootFilesystem := false
202+
203+
return &corev1.Pod{
204+
ObjectMeta: metav1.ObjectMeta{
205+
Name: c.podName,
206+
Namespace: c.namespace,
207+
Labels: c.makeCatalogSourcePodLabels(),
208+
},
209+
Spec: corev1.PodSpec{
210+
Containers: []corev1.Container{
211+
{
212+
Name: "catalog",
213+
Image: image,
214+
Command: []string{"opm", "serve", catalogMountPath},
215+
Ports: []corev1.ContainerPort{
216+
{
217+
Name: "grpc",
218+
ContainerPort: 50051,
219+
},
220+
},
221+
ReadinessProbe: &corev1.Probe{
222+
Handler: corev1.Handler{
223+
Exec: &corev1.ExecAction{
224+
Command: []string{"grpc_health_probe", "-addr=:50051"},
225+
},
226+
},
227+
InitialDelaySeconds: readinessDelay,
228+
TimeoutSeconds: 5,
229+
},
230+
LivenessProbe: &corev1.Probe{
231+
Handler: corev1.Handler{
232+
Exec: &corev1.ExecAction{
233+
Command: []string{"grpc_health_probe", "-addr=:50051"},
234+
},
235+
},
236+
InitialDelaySeconds: livenessDelay,
237+
TimeoutSeconds: 5,
238+
},
239+
Resources: corev1.ResourceRequirements{
240+
Requests: corev1.ResourceList{
241+
corev1.ResourceCPU: resource.MustParse("10m"),
242+
corev1.ResourceMemory: resource.MustParse("50Mi"),
243+
},
244+
},
245+
SecurityContext: &corev1.SecurityContext{
246+
ReadOnlyRootFilesystem: &readOnlyRootFilesystem,
247+
},
248+
ImagePullPolicy: corev1.PullAlways,
249+
TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
250+
VolumeMounts: []corev1.VolumeMount{
251+
{
252+
Name: volumeMountName,
253+
MountPath: catalogMountPath,
254+
ReadOnly: true,
255+
},
256+
},
257+
},
258+
},
259+
Volumes: []corev1.Volume{
260+
{
261+
Name: volumeMountName,
262+
VolumeSource: corev1.VolumeSource{
263+
ConfigMap: &corev1.ConfigMapVolumeSource{
264+
LocalObjectReference: corev1.LocalObjectReference{
265+
Name: c.configMapName,
266+
},
267+
},
268+
},
269+
},
270+
},
271+
},
272+
}
273+
}
274+
275+
func (c *magicCatalog) makeCatalogSourcePodLabels() map[string]string {
276+
return map[string]string{
277+
olmCatalogLabel: c.name,
278+
}
279+
}

Diff for: test/e2e/magic_catalog_test.go

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package e2e
2+
3+
import (
4+
"context"
5+
. "github.com/onsi/ginkgo"
6+
. "github.com/onsi/gomega"
7+
"github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx"
8+
corev1 "k8s.io/api/core/v1"
9+
)
10+
11+
var _ = Describe("MagicCatalog", func() {
12+
var (
13+
generatedNamespace corev1.Namespace
14+
)
15+
16+
BeforeEach(func() {
17+
generatedNamespace = SetupGeneratedTestNamespace(genName("magic-catalgo-e2e-"))
18+
})
19+
20+
AfterEach(func() {
21+
TeardownNamespace(generatedNamespace.GetName())
22+
})
23+
24+
It("Deploys and Undeploys a File-based Catalog", func() {
25+
// create dependencies
26+
const catalogName = "test"
27+
namespace := generatedNamespace.GetName()
28+
kubeClient := ctx.Ctx().Client()
29+
provider, err := NewFileBasedFiledBasedCatalogProvider("../test/e2e/testdata/fbc_catalog.json")
30+
Expect(err).To(BeNil())
31+
32+
// create and deploy and undeploy the magic catalog
33+
magicCatalog := NewMagicCatalog(kubeClient, namespace, catalogName, provider)
34+
35+
// deployment blocks until the catalog source has reached a READY status
36+
Expect(magicCatalog.DeployCatalog(context.TODO())).To(BeNil())
37+
Expect(magicCatalog.UndeployCatalog(context.TODO())).To(BeNil())
38+
})
39+
})

Diff for: test/e2e/testdata/fbc_catalog.json

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"schema": "olm.package",
3+
"name": "packageA",
4+
"defaultChannel": "stable"
5+
}
6+
{
7+
"schema": "olm.channel",
8+
"name": "stable",
9+
"package": "packageA",
10+
"entries": [
11+
{
12+
"name": "packageA.v1.0.0"
13+
}
14+
]
15+
}
16+
{
17+
"schema": "olm.bundle",
18+
"name": "packageA.v1.0.0",
19+
"package": "packageA",
20+
"image": "packageA:v1.0.0",
21+
"properties": [
22+
{
23+
"type": "olm.gvk",
24+
"value": {
25+
"group": "example.com",
26+
"kind": "TestA",
27+
"version": "v1alpha1"
28+
}
29+
},
30+
{
31+
"type": "olm.package",
32+
"value": {
33+
"packageName": "packageA",
34+
"version": "1.0.0"
35+
}
36+
}
37+
]
38+
}

0 commit comments

Comments
 (0)