Skip to content

Commit a36ed09

Browse files
Merge pull request #674 from njhale/support-services
fix(installplans): add ability to apply Services
2 parents f3b9375 + 5941c06 commit a36ed09

File tree

6 files changed

+204
-15
lines changed

6 files changed

+204
-15
lines changed

pkg/api/apis/operators/v1alpha1/clusterserviceversion_types.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,4 +479,3 @@ func (csv ClusterServiceVersion) GetOwnedAPIServiceDescriptions() []APIServiceDe
479479

480480
return descs
481481
}
482-

pkg/api/apis/operators/v1alpha1/installplan_types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import (
44
"errors"
55
"fmt"
66

7-
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators"
87
corev1 "k8s.io/api/core/v1"
98
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
10+
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators"
1011
)
1112

1213
const (

pkg/controller/operators/catalog/operator.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const (
4444
clusterRoleKind = "ClusterRole"
4545
clusterRoleBindingKind = "ClusterRoleBinding"
4646
serviceAccountKind = "ServiceAccount"
47+
serviceKind = "Service"
4748
roleKind = "Role"
4849
roleBindingKind = "RoleBinding"
4950
)
@@ -1115,6 +1116,41 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
11151116
plan.Status.Plan[i].Status = v1alpha1.StepStatusCreated
11161117
}
11171118

1119+
case serviceKind:
1120+
// Marshal the manifest into a Service instance
1121+
var s corev1.Service
1122+
err := json.Unmarshal([]byte(step.Resource.Manifest), &s)
1123+
if err != nil {
1124+
return errorwrap.Wrapf(err, "error parsing step manifest: %s", step.Resource.Name)
1125+
}
1126+
1127+
// Update UIDs on all CSV OwnerReferences
1128+
updated, err := o.getUpdatedOwnerReferences(s.OwnerReferences, plan.Namespace)
1129+
if err != nil {
1130+
return errorwrap.Wrapf(err, "error generating ownerrefs for service: %s", s.GetName())
1131+
}
1132+
s.SetOwnerReferences(updated)
1133+
s.SetNamespace(namespace)
1134+
1135+
// Attempt to create the Service
1136+
_, err = o.OpClient.KubernetesInterface().CoreV1().Services(plan.Namespace).Create(&s)
1137+
if k8serrors.IsAlreadyExists(err) {
1138+
// If it already exists we need to patch the existing SA with the new OwnerReferences
1139+
s.SetNamespace(plan.Namespace)
1140+
_, err = o.OpClient.UpdateService(&s)
1141+
if err != nil {
1142+
return errorwrap.Wrapf(err, "error updating service: %s", s.GetName())
1143+
}
1144+
1145+
// Mark as present
1146+
plan.Status.Plan[i].Status = v1alpha1.StepStatusPresent
1147+
} else if err != nil {
1148+
return errorwrap.Wrapf(err, "error creating service: %s", s.GetName())
1149+
} else {
1150+
// If no error occurred, mark the step as Created
1151+
plan.Status.Plan[i].Status = v1alpha1.StepStatusCreated
1152+
}
1153+
11181154
default:
11191155
return v1alpha1.ErrInvalidInstallPlan
11201156
}

pkg/controller/operators/catalog/operator_test.go

Lines changed: 139 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package catalog
22

33
import (
4+
"encoding/json"
45
"errors"
56
"fmt"
67
"testing"
@@ -9,7 +10,9 @@ import (
910
"github.com/ghodss/yaml"
1011
"github.com/sirupsen/logrus"
1112
"github.com/stretchr/testify/require"
13+
appsv1 "k8s.io/api/apps/v1"
1214
corev1 "k8s.io/api/core/v1"
15+
rbacv1 "k8s.io/api/rbac/v1"
1316
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
1417
apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
1518
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -113,6 +116,98 @@ func TestTransitionInstallPlan(t *testing.T) {
113116
}
114117
}
115118

119+
func TestExecutePlan(t *testing.T) {
120+
namespace := "ns"
121+
122+
tests := []struct {
123+
testName string
124+
in *v1alpha1.InstallPlan
125+
want []runtime.Object
126+
err error
127+
}{
128+
{
129+
testName: "NoSteps",
130+
in: installPlan("p", namespace, v1alpha1.InstallPlanPhaseInstalling),
131+
want: []runtime.Object{},
132+
err: nil,
133+
},
134+
{
135+
testName: "MultipleSteps",
136+
in: withSteps(installPlan("p", namespace, v1alpha1.InstallPlanPhaseInstalling, "csv"),
137+
[]*v1alpha1.Step{
138+
&v1alpha1.Step{
139+
Resource: v1alpha1.StepResource{
140+
CatalogSource: "catalog",
141+
CatalogSourceNamespace: namespace,
142+
Group: "",
143+
Version: "v1",
144+
Kind: "Service",
145+
Name: "service",
146+
Manifest: toManifest(service("service", namespace)),
147+
},
148+
Status: v1alpha1.StepStatusUnknown,
149+
},
150+
&v1alpha1.Step{
151+
Resource: v1alpha1.StepResource{
152+
CatalogSource: "catalog",
153+
CatalogSourceNamespace: namespace,
154+
Group: "operators.coreos.com",
155+
Version: "v1alpha1",
156+
Kind: "ClusterServiceVersion",
157+
Name: "csv",
158+
Manifest: toManifest(csv("csv", namespace, nil, nil)),
159+
},
160+
Status: v1alpha1.StepStatusUnknown,
161+
},
162+
},
163+
),
164+
want: []runtime.Object{service("service", namespace), csv("csv", namespace, nil, nil)},
165+
err: nil,
166+
},
167+
}
168+
169+
for _, tt := range tests {
170+
t.Run(tt.testName, func(t *testing.T) {
171+
stopCh := make(chan struct{})
172+
defer func() { stopCh <- struct{}{} }()
173+
op, _, err := NewFakeOperator([]runtime.Object{tt.in}, nil, nil, nil, namespace, stopCh)
174+
require.NoError(t, err)
175+
176+
err = op.ExecutePlan(tt.in)
177+
require.Equal(t, tt.err, err)
178+
179+
for _, obj := range tt.want {
180+
var err error
181+
var fetched runtime.Object
182+
switch o := obj.(type) {
183+
case *appsv1.Deployment:
184+
fetched, err = op.OpClient.GetDeployment(namespace, o.GetName())
185+
case *rbacv1.ClusterRole:
186+
fetched, err = op.OpClient.GetClusterRole(o.GetName())
187+
case *rbacv1.Role:
188+
fetched, err = op.OpClient.GetRole(namespace, o.GetName())
189+
case *rbacv1.ClusterRoleBinding:
190+
fetched, err = op.OpClient.GetClusterRoleBinding(o.GetName())
191+
case *rbacv1.RoleBinding:
192+
fetched, err = op.OpClient.GetRoleBinding(namespace, o.GetName())
193+
case *corev1.ServiceAccount:
194+
fetched, err = op.OpClient.GetServiceAccount(namespace, o.GetName())
195+
case *corev1.Service:
196+
fetched, err = op.OpClient.GetService(namespace, o.GetName())
197+
case *v1alpha1.ClusterServiceVersion:
198+
fetched, err = op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Get(o.GetName(), metav1.GetOptions{})
199+
default:
200+
require.Failf(t, "couldn't find expected object", "%#v", obj)
201+
}
202+
203+
require.NoError(t, err, "couldn't fetch %s %v", namespace, obj)
204+
fmt.Printf("fetched: %v", fetched)
205+
require.EqualValues(t, obj, fetched)
206+
}
207+
})
208+
}
209+
}
210+
116211
func TestSyncCatalogSources(t *testing.T) {
117212
tests := []struct {
118213
testName string
@@ -284,21 +379,21 @@ func TestCompetingCRDOwnersExist(t *testing.T) {
284379
testNamespace := "default"
285380
tests := []struct {
286381
name string
287-
csv v1alpha1.ClusterServiceVersion
382+
csv *v1alpha1.ClusterServiceVersion
288383
existingCRDOwners map[string][]string
289384
expectedErr error
290385
expectedResult bool
291386
}{
292387
{
293388
name: "NoCompetingOwnersExist",
294-
csv: csv("turkey", []string{"feathers"}, nil),
389+
csv: csv("turkey", testNamespace, []string{"feathers"}, nil),
295390
existingCRDOwners: nil,
296391
expectedErr: nil,
297392
expectedResult: false,
298393
},
299394
{
300395
name: "OnlyCompetingWithSelf",
301-
csv: csv("turkey", []string{"feathers"}, nil),
396+
csv: csv("turkey", testNamespace, []string{"feathers"}, nil),
302397
existingCRDOwners: map[string][]string{
303398
"feathers": {"turkey"},
304399
},
@@ -307,7 +402,7 @@ func TestCompetingCRDOwnersExist(t *testing.T) {
307402
},
308403
{
309404
name: "CompetingOwnersExist",
310-
csv: csv("turkey", []string{"feathers"}, nil),
405+
csv: csv("turkey", testNamespace, []string{"feathers"}, nil),
311406
existingCRDOwners: map[string][]string{
312407
"feathers": {"seagull"},
313408
},
@@ -316,7 +411,7 @@ func TestCompetingCRDOwnersExist(t *testing.T) {
316411
},
317412
{
318413
name: "CompetingOwnerExistsOnSecondCRD",
319-
csv: csv("turkey", []string{"feathers", "beak"}, nil),
414+
csv: csv("turkey", testNamespace, []string{"feathers", "beak"}, nil),
320415
existingCRDOwners: map[string][]string{
321416
"milk": {"cow"},
322417
"beak": {"squid"},
@@ -326,7 +421,7 @@ func TestCompetingCRDOwnersExist(t *testing.T) {
326421
},
327422
{
328423
name: "MoreThanOneCompetingOwnerExists",
329-
csv: csv("turkey", []string{"feathers"}, nil),
424+
csv: csv("turkey", testNamespace, []string{"feathers"}, nil),
330425
existingCRDOwners: map[string][]string{
331426
"feathers": {"seagull", "turkey"},
332427
},
@@ -338,7 +433,7 @@ func TestCompetingCRDOwnersExist(t *testing.T) {
338433
t.Run(tt.name, func(t *testing.T) {
339434
t.Parallel()
340435

341-
competing, err := competingCRDOwnersExist(testNamespace, &tt.csv, tt.existingCRDOwners)
436+
competing, err := competingCRDOwnersExist(testNamespace, tt.csv, tt.existingCRDOwners)
342437

343438
// Assert the error is as expected
344439
if tt.expectedErr == nil {
@@ -440,31 +535,48 @@ func NewFakeOperator(clientObjs []runtime.Object, k8sObjs []runtime.Object, extO
440535
return op, hasSyncedCheckFns, nil
441536
}
442537

443-
func installPlan(names ...string) v1alpha1.InstallPlan {
444-
return v1alpha1.InstallPlan{
538+
func installPlan(name, namespace string, phase v1alpha1.InstallPlanPhase, names ...string) *v1alpha1.InstallPlan {
539+
return &v1alpha1.InstallPlan{
540+
ObjectMeta: metav1.ObjectMeta{
541+
Name: name,
542+
Namespace: namespace,
543+
},
445544
Spec: v1alpha1.InstallPlanSpec{
446545
ClusterServiceVersionNames: names,
447546
},
448547
Status: v1alpha1.InstallPlanStatus{
449-
Plan: []*v1alpha1.Step{},
548+
Phase: phase,
549+
Plan: []*v1alpha1.Step{},
450550
},
451551
}
452552
}
453553

454-
func csv(name string, owned, required []string) v1alpha1.ClusterServiceVersion {
554+
func withSteps(plan *v1alpha1.InstallPlan, steps []*v1alpha1.Step) *v1alpha1.InstallPlan {
555+
plan.Status.Plan = steps
556+
return plan
557+
}
558+
559+
func csv(name, namespace string, owned, required []string) *v1alpha1.ClusterServiceVersion {
455560
requiredCRDDescs := make([]v1alpha1.CRDDescription, 0)
456561
for _, name := range required {
457562
requiredCRDDescs = append(requiredCRDDescs, v1alpha1.CRDDescription{Name: name, Version: "v1", Kind: name})
458563
}
564+
if len(requiredCRDDescs) == 0 {
565+
requiredCRDDescs = nil
566+
}
459567

460568
ownedCRDDescs := make([]v1alpha1.CRDDescription, 0)
461569
for _, name := range owned {
462570
ownedCRDDescs = append(ownedCRDDescs, v1alpha1.CRDDescription{Name: name, Version: "v1", Kind: name})
463571
}
572+
if len(ownedCRDDescs) == 0 {
573+
ownedCRDDescs = nil
574+
}
464575

465-
return v1alpha1.ClusterServiceVersion{
576+
return &v1alpha1.ClusterServiceVersion{
466577
ObjectMeta: metav1.ObjectMeta{
467-
Name: name,
578+
Name: name,
579+
Namespace: namespace,
468580
},
469581
Spec: v1alpha1.ClusterServiceVersionSpec{
470582
CustomResourceDefinitions: v1alpha1.CustomResourceDefinitions{
@@ -489,3 +601,17 @@ func crd(name string) v1beta1.CustomResourceDefinition {
489601
},
490602
}
491603
}
604+
605+
func service(name, namespace string) *corev1.Service {
606+
return &corev1.Service{
607+
ObjectMeta: metav1.ObjectMeta{
608+
Name: name,
609+
Namespace: namespace,
610+
},
611+
}
612+
}
613+
614+
func toManifest(obj runtime.Object) string {
615+
raw, _ := json.Marshal(obj)
616+
return string(raw)
617+
}

pkg/controller/registry/resolver/resolver_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
opregistry "github.com/operator-framework/operator-registry/pkg/registry"
99
"github.com/stretchr/testify/require"
10+
corev1 "k8s.io/api/core/v1"
1011
rbacv1 "k8s.io/api/rbac/v1"
1112
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1213
"k8s.io/client-go/tools/cache"
@@ -113,6 +114,26 @@ func TestNamespaceResolver(t *testing.T) {
113114
},
114115
},
115116
},
117+
{
118+
name: "SingleNewSubscription/ResolveOne/AdditionalBundleObjects/Service",
119+
clusterState: []runtime.Object{
120+
newSub(namespace, "a", "alpha", catalog),
121+
},
122+
bundlesInCatalog: []*opregistry.Bundle{
123+
withBundleObject(bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil), u(&corev1.Service{TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: ""}, ObjectMeta: metav1.ObjectMeta{Name: "test-service"}})),
124+
bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
125+
},
126+
out: out{
127+
steps: [][]*v1alpha1.Step{
128+
bundleSteps(bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil), namespace, catalog),
129+
bundleSteps(withBundleObject(bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil), u(&corev1.Service{TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: ""}, ObjectMeta: metav1.ObjectMeta{Name: "test-service"}})), namespace, catalog),
130+
subSteps(namespace, "b.v1", "b", "beta", catalog),
131+
},
132+
subs: []*v1alpha1.Subscription{
133+
updatedSub(namespace, "a.v1", "a", "alpha", catalog),
134+
},
135+
},
136+
},
116137
{
117138
name: "SingleNewSubscription/DependencyMissing",
118139
clusterState: []runtime.Object{

pkg/lib/ownerutil/util.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ func InferGroupVersionKind(obj runtime.Object) error {
201201
}
202202

203203
switch obj.(type) {
204+
case *corev1.Service:
205+
objectKind.SetGroupVersionKind(schema.GroupVersionKind{
206+
Group: "",
207+
Version: "v1",
208+
Kind: "Service",
209+
})
204210
case *corev1.ServiceAccount:
205211
objectKind.SetGroupVersionKind(schema.GroupVersionKind{
206212
Group: "",

0 commit comments

Comments
 (0)