Skip to content

Commit 7b1b46a

Browse files
paulatulisa-cordier
authored andcommitted
fix(subscription): do not panic when validating a subscription to an API without context (#1781)
(cherry picked from commit 43db68d) # Conflicts: # internal/admission/subscription/validate.go
1 parent b57cdf1 commit 7b1b46a

4 files changed

Lines changed: 182 additions & 6 deletions

File tree

internal/admission/subscription/validate.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func validateCreate(ctx context.Context, obj runtime.Object) *errors.AdmissionEr
137137
return errs
138138
}
139139

140-
errs.Add(validateContextRefs(api, app))
140+
errs.Add(ValidateContextRefs(api, app))
141141
if errs.IsSevere() {
142142
return errs
143143
}
@@ -258,12 +258,22 @@ func validateApiKind(sub core.SubscriptionObject) *errors.AdmissionError {
258258
return nil
259259
}
260260

261-
func validateContextRefs(api core.ApiDefinitionObject, app core.ApplicationObject) *errors.AdmissionError {
262-
apiCtx, appCtx := api.ContextRef(), app.ContextRef()
261+
func ValidateContextRefs(api core.ApiDefinitionObject, app core.ApplicationObject) *errors.AdmissionError {
262+
if !api.HasContext() {
263+
return errors.NewSeveref(
264+
"unable to subscribe to API [%s] because it does not reference a management context",
265+
api.GetRef(),
266+
)
267+
}
268+
269+
if !app.HasContext() {
270+
return errors.NewSeveref(
271+
"unable to subscribe from application [%s] because it does not reference a management context",
272+
app.GetRef(),
273+
)
274+
}
263275

264-
mismatch := appCtx.GetName() != apiCtx.GetName()
265-
mismatch = mismatch || appCtx.GetNamespace() != apiCtx.GetNamespace()
266-
if mismatch {
276+
if !sameContextRef(api.ContextRef(), app.ContextRef()) {
267277
return errors.NewSeveref(
268278
"management contexts must match between application [%s] and API [%s], got [%v] and [%v]",
269279
app.GetRef(),
@@ -272,9 +282,16 @@ func validateContextRefs(api core.ApiDefinitionObject, app core.ApplicationObjec
272282
api.ContextRef(),
273283
)
274284
}
285+
275286
return nil
276287
}
277288

289+
// sameContextRef compares two management context references. Both are expected to be
290+
// non nil, which callers guarantee by checking HasContext beforehand.
291+
func sameContextRef(left core.ObjectRef, right core.ObjectRef) bool {
292+
return left.GetName() == right.GetName() && left.GetNamespace() == right.GetNamespace()
293+
}
294+
278295
func validateEndingAt(endingAt *string) *errors.AdmissionError {
279296
if endingAt != nil {
280297
t, err := time.Parse(time.RFC3339, *endingAt)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (C) 2015 The Gravitee team (http://gravitee.io)
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 subscription
16+
17+
import (
18+
"context"
19+
20+
adm "github.com/gravitee-io/gravitee-kubernetes-operator/internal/admission/subscription"
21+
"github.com/gravitee-io/gravitee-kubernetes-operator/internal/errors"
22+
"github.com/gravitee-io/gravitee-kubernetes-operator/test/internal/integration/assert"
23+
"github.com/gravitee-io/gravitee-kubernetes-operator/test/internal/integration/constants"
24+
"github.com/gravitee-io/gravitee-kubernetes-operator/test/internal/integration/fixture"
25+
"github.com/gravitee-io/gravitee-kubernetes-operator/test/internal/integration/labels"
26+
"github.com/gravitee-io/gravitee-kubernetes-operator/test/internal/integration/random"
27+
28+
. "github.com/onsi/ginkgo/v2"
29+
30+
. "github.com/onsi/gomega"
31+
)
32+
33+
var _ = Describe("Validate create", labels.WithContext, func() {
34+
ctx := context.Background()
35+
admissionCtrl := adm.AdmissionCtrl{}
36+
37+
fixtures := fixture.
38+
Builder().
39+
WithAPIv4(constants.ApiV4WithJWTPlanFile).
40+
WithApplication(constants.ApplicationWithClientIDFile).
41+
WithSubscription(constants.SubscriptionFile).
42+
WithContext(constants.ContextWithCredentialsFile).
43+
Build()
44+
45+
clientID := random.GetName()
46+
fixtures.Application.Spec.Settings.App.ClientID = &clientID
47+
48+
// An API is not required to reference a management context, while an application is.
49+
fixtures.APIv4.Spec.Context = nil
50+
51+
// The subscription is submitted to the admission controller directly and never applied:
52+
// the reconciler cannot build an APIM client for an API without a contextRef, so the
53+
// resource would never reach a terminal state.
54+
subscription := fixtures.Subscription
55+
fixtures.Subscription = nil
56+
57+
fixtures.Apply()
58+
59+
It("should reject the subscription if the API has no context", func() {
60+
Eventually(func() error {
61+
Expect(admissionCtrl.Default(ctx, subscription)).ToNot(HaveOccurred())
62+
_, err := admissionCtrl.ValidateCreate(ctx, subscription)
63+
return assert.Equals(
64+
"error",
65+
errors.NewSeveref(
66+
"unable to subscribe to API [%s] because it does not reference a management context",
67+
fixtures.APIv4.GetRef(),
68+
),
69+
err,
70+
)
71+
}, constants.EventualTimeout, constants.Interval).Should(Succeed())
72+
})
73+
})
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (C) 2015 The Gravitee team (http://gravitee.io)
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 subscription
16+
17+
import (
18+
"testing"
19+
20+
. "github.com/onsi/ginkgo/v2"
21+
. "github.com/onsi/gomega"
22+
//+kubebuilder:scaffold:imports
23+
)
24+
25+
func TestSubscriptionAdmission(t *testing.T) {
26+
RegisterFailHandler(Fail)
27+
RunSpecs(t, "Subscription admission hook tests suite")
28+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (C) 2015 The Gravitee team (http://gravitee.io)
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 subscription
16+
17+
import (
18+
"github.com/gravitee-io/gravitee-kubernetes-operator/api/model/refs"
19+
"github.com/gravitee-io/gravitee-kubernetes-operator/api/v1alpha1"
20+
"github.com/gravitee-io/gravitee-kubernetes-operator/internal/admission/subscription"
21+
. "github.com/onsi/ginkgo/v2"
22+
. "github.com/onsi/gomega"
23+
)
24+
25+
func contextRef(namespace, name string) *refs.NamespacedName {
26+
return &refs.NamespacedName{Namespace: namespace, Name: name}
27+
}
28+
29+
// A contextRef is optional on API definitions, so it reaches validation as an interface
30+
// wrapping a nil pointer. Every combination must yield a verdict rather than a panic.
31+
var _ = Describe("Validating context refs", func() {
32+
DescribeTable("subscribing an application to an API",
33+
func(apiContext, appContext *refs.NamespacedName, expectRejection bool) {
34+
api := &v1alpha1.ApiV4Definition{Spec: v1alpha1.ApiV4DefinitionSpec{Context: apiContext}}
35+
app := &v1alpha1.Application{Spec: v1alpha1.ApplicationSpec{Context: appContext}}
36+
37+
err := subscription.ValidateContextRefs(api, app)
38+
39+
if expectRejection {
40+
Expect(err).ToNot(BeNil())
41+
return
42+
}
43+
Expect(err).To(BeNil())
44+
},
45+
Entry("is accepted when both reference the same context",
46+
contextRef("default", "dev-ctx"), contextRef("default", "dev-ctx"), false),
47+
Entry("is rejected when the context names differ",
48+
contextRef("default", "prod-ctx"), contextRef("default", "dev-ctx"), true),
49+
Entry("is rejected when the context namespaces differ",
50+
contextRef("other", "dev-ctx"), contextRef("default", "dev-ctx"), true),
51+
Entry("is rejected when the API has no context",
52+
nil, contextRef("default", "dev-ctx"), true),
53+
Entry("is rejected when the application has no context",
54+
contextRef("default", "dev-ctx"), nil, true),
55+
Entry("is rejected when neither has a context",
56+
nil, nil, true),
57+
)
58+
})

0 commit comments

Comments
 (0)