Skip to content

Commit f582595

Browse files
committed
Implement the admission framework for VWs
On-behalf-of: @SAP [email protected] Signed-off-by: Marko Mudrinić <[email protected]>
1 parent e86d128 commit f582595

File tree

12 files changed

+463
-1
lines changed

12 files changed

+463
-1
lines changed

cmd/virtual-workspaces/command/cmd.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import (
4444
kcpfeatures "github.com/kcp-dev/kcp/pkg/features"
4545
"github.com/kcp-dev/kcp/pkg/server/bootstrap"
4646
virtualrootapiserver "github.com/kcp-dev/kcp/pkg/virtual/framework/rootapiserver"
47+
virtualoptions "github.com/kcp-dev/kcp/pkg/virtual/options"
4748
kcpclientset "github.com/kcp-dev/kcp/sdk/client/clientset/versioned/cluster"
4849
kcpinformers "github.com/kcp-dev/kcp/sdk/client/informers/externalversions"
4950
)
@@ -178,6 +179,13 @@ func Run(ctx context.Context, o *options.Options) error {
178179
return err
179180
}
180181

182+
admissionOptions := virtualoptions.NewAdmission()
183+
if err := admissionOptions.ApplyTo(&recommendedConfig.Config, func() []virtualrootapiserver.NamedVirtualWorkspace {
184+
return rootAPIServerConfig.Extra.VirtualWorkspaces
185+
}); err != nil {
186+
return err
187+
}
188+
181189
sharedExternalURLGetter := func() string {
182190
return o.ShardExternalURL
183191
}

pkg/server/virtual.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ func newVirtualConfig(
8585
return nil, err
8686
}
8787

88+
admissionOptions := virtualoptions.NewAdmission()
89+
if err := admissionOptions.ApplyTo(&recommendedConfig.Config, func() []virtualrootapiserver.NamedVirtualWorkspace {
90+
return c.Extra.VirtualWorkspaces
91+
}); err != nil {
92+
return nil, err
93+
}
94+
8895
c.Extra.VirtualWorkspaces, err = o.Virtual.VirtualWorkspaces.NewVirtualWorkspaces(
8996
config,
9097
virtualcommandoptions.DefaultRootPathPrefix,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
Copyright 2025 The KCP Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package initializers
18+
19+
import (
20+
"k8s.io/apiserver/pkg/admission"
21+
22+
"github.com/kcp-dev/kcp/pkg/virtual/framework/rootapiserver"
23+
)
24+
25+
// NewVirtualWorkspacesInitializer returns an admission plugin initializer that injects
26+
// both virtual workspaces factories into admission plugins.
27+
func NewVirtualWorkspacesInitializer(
28+
virtualWorkspaces func() []rootapiserver.NamedVirtualWorkspace,
29+
) *virtualWorkspacesInitializer {
30+
return &virtualWorkspacesInitializer{
31+
virtualWorkspaces: virtualWorkspaces,
32+
}
33+
}
34+
35+
type virtualWorkspacesInitializer struct {
36+
virtualWorkspaces func() []rootapiserver.NamedVirtualWorkspace
37+
}
38+
39+
func (i *virtualWorkspacesInitializer) Initialize(plugin admission.Interface) {
40+
if wants, ok := plugin.(WantsVirtualWorkspaces); ok {
41+
wants.SetVirtualWorkspaces(i.virtualWorkspaces)
42+
}
43+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
Copyright 2025 The KCP Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package initializers
18+
19+
import (
20+
"github.com/kcp-dev/kcp/pkg/virtual/framework/rootapiserver"
21+
)
22+
23+
// WantsVirtualWorkspaces interface should be implemented by admission plugins
24+
// that want to have virtual workspaces injected.
25+
type WantsVirtualWorkspaces interface {
26+
SetVirtualWorkspaces(virtualWorkspaces func() []rootapiserver.NamedVirtualWorkspace)
27+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
Copyright 2025 The KCP Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package admission
18+
19+
import (
20+
"context"
21+
22+
"k8s.io/apiserver/pkg/admission"
23+
)
24+
25+
/*
26+
These interfaces are supposed to be used by the virtual workspace admission plugins.
27+
We're intentionally not using the Kubernetes admission interfaces as those require an
28+
additional function, Handle, which we don't need in admission for virtual workspaces.
29+
*/
30+
31+
// Mutator is an abstract, pluggable interface for Admission Control decisions.
32+
type Mutator interface {
33+
// Admit makes an admission decision based on the request attributes.
34+
// Context is used only for timeout/deadline/cancellation and tracing information.
35+
Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error)
36+
}
37+
38+
// Validator is an abstract, pluggable interface for Admission Control decisions.
39+
type Validator interface {
40+
// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate
41+
// Context is used only for timeout/deadline/cancellation and tracing information.
42+
Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error)
43+
}
44+
45+
// MutatorValidator is a union of Mutator and Validator interfaces.
46+
type MutatorValidator interface {
47+
Mutator
48+
Validator
49+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
Copyright 2025 The KCP Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package mutatingwebhook
18+
19+
import (
20+
"context"
21+
"errors"
22+
"fmt"
23+
"io"
24+
25+
kerrors "k8s.io/apimachinery/pkg/api/errors"
26+
"k8s.io/apiserver/pkg/admission"
27+
28+
vwinitializers "github.com/kcp-dev/kcp/pkg/virtual/framework/admission/initializers"
29+
virtualcontext "github.com/kcp-dev/kcp/pkg/virtual/framework/context"
30+
"github.com/kcp-dev/kcp/pkg/virtual/framework/rootapiserver"
31+
)
32+
33+
const (
34+
PluginName = "apis.kcp.io/VWMutatingWebhook"
35+
)
36+
37+
type Plugin struct {
38+
*admission.Handler
39+
40+
virtualWorkspaces func() []rootapiserver.NamedVirtualWorkspace
41+
}
42+
43+
var (
44+
_ = admission.MutationInterface(&Plugin{})
45+
_ = admission.InitializationValidator(&Plugin{})
46+
_ = vwinitializers.WantsVirtualWorkspaces(&Plugin{})
47+
)
48+
49+
func NewMutatingAdmissionWebhook() (*Plugin, error) {
50+
return &Plugin{
51+
// We're handling all possible operations, it's up to the plugin implemention to decide which one to
52+
// handle.
53+
Handler: admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update),
54+
}, nil
55+
}
56+
57+
func Register(plugins *admission.Plugins) {
58+
plugins.Register(PluginName, func(_ io.Reader) (admission.Interface, error) {
59+
return NewMutatingAdmissionWebhook()
60+
})
61+
}
62+
63+
func (p *Plugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
64+
if err := p.ValidateInitialization(); err != nil {
65+
return kerrors.NewInternalError(fmt.Errorf("error validating MutatingWebhook initialization: %w", err))
66+
}
67+
68+
virtualWorkspaceName, _ := virtualcontext.VirtualWorkspaceNameFrom(ctx)
69+
if virtualWorkspaceName == "" {
70+
return kerrors.NewBadRequest("path not resolved to a valid virtual workspace")
71+
}
72+
73+
for _, vw := range p.virtualWorkspaces() {
74+
if vw.Name == virtualWorkspaceName {
75+
return vw.VirtualWorkspace.Admit(ctx, attr, o)
76+
}
77+
}
78+
79+
return nil
80+
}
81+
82+
func (p *Plugin) ValidateInitialization() error {
83+
if p.virtualWorkspaces == nil {
84+
return errors.New("missing virtualWorkspaces")
85+
}
86+
return nil
87+
}
88+
89+
func (p *Plugin) SetVirtualWorkspaces(virtualWorkspaces func() []rootapiserver.NamedVirtualWorkspace) {
90+
p.virtualWorkspaces = virtualWorkspaces
91+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
Copyright 2025 The KCP Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package plugins
18+
19+
import (
20+
"k8s.io/apiserver/pkg/admission"
21+
22+
"github.com/kcp-dev/kcp/pkg/virtual/framework/admission/mutatingwebhook"
23+
"github.com/kcp-dev/kcp/pkg/virtual/framework/admission/validatingwebhook"
24+
)
25+
26+
// AllOrderedPlugins is the list of all the plugins in order.
27+
var AllOrderedPlugins = []string{
28+
mutatingwebhook.PluginName,
29+
validatingwebhook.PluginName,
30+
}
31+
32+
// RegisterAllVWAdmissionPlugins registers all admission plugins.
33+
// The order of registration is irrelevant, see AllOrderedPlugins for execution order.
34+
func RegisterAllVWAdmissionPlugins(plugins *admission.Plugins) {
35+
mutatingwebhook.Register(plugins)
36+
validatingwebhook.Register(plugins)
37+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
Copyright 2025 The KCP Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package validatingwebhook
18+
19+
import (
20+
"context"
21+
"errors"
22+
"fmt"
23+
"io"
24+
25+
kerrors "k8s.io/apimachinery/pkg/api/errors"
26+
"k8s.io/apiserver/pkg/admission"
27+
28+
vwinitializers "github.com/kcp-dev/kcp/pkg/virtual/framework/admission/initializers"
29+
virtualcontext "github.com/kcp-dev/kcp/pkg/virtual/framework/context"
30+
"github.com/kcp-dev/kcp/pkg/virtual/framework/rootapiserver"
31+
)
32+
33+
const (
34+
PluginName = "apis.kcp.io/qqqqqq"
35+
)
36+
37+
type Plugin struct {
38+
*admission.Handler
39+
40+
virtualWorkspaces func() []rootapiserver.NamedVirtualWorkspace
41+
}
42+
43+
var (
44+
_ = admission.ValidationInterface(&Plugin{})
45+
_ = admission.InitializationValidator(&Plugin{})
46+
_ = vwinitializers.WantsVirtualWorkspaces(&Plugin{})
47+
)
48+
49+
func NewValidatingAdmissionWebhook() (*Plugin, error) {
50+
return &Plugin{
51+
// We're handling all possible operations, it's up to the plugin implemention to decide which one to
52+
// handle.
53+
Handler: admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update),
54+
}, nil
55+
}
56+
57+
func Register(plugins *admission.Plugins) {
58+
plugins.Register(PluginName, func(_ io.Reader) (admission.Interface, error) {
59+
return NewValidatingAdmissionWebhook()
60+
})
61+
}
62+
63+
func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
64+
if err := p.ValidateInitialization(); err != nil {
65+
return kerrors.NewInternalError(fmt.Errorf("error validating MutatingWebhook initialization: %w", err))
66+
}
67+
68+
virtualWorkspaceName, _ := virtualcontext.VirtualWorkspaceNameFrom(ctx)
69+
if virtualWorkspaceName == "" {
70+
return kerrors.NewBadRequest("path not resolved to a valid virtual workspace")
71+
}
72+
73+
for _, vw := range p.virtualWorkspaces() {
74+
if vw.Name == virtualWorkspaceName {
75+
return vw.VirtualWorkspace.Validate(ctx, a, o)
76+
}
77+
}
78+
79+
return nil
80+
}
81+
82+
func (p *Plugin) ValidateInitialization() error {
83+
if p.virtualWorkspaces == nil {
84+
return errors.New("missing virtualWorkspaces")
85+
}
86+
return nil
87+
}
88+
89+
func (p *Plugin) SetVirtualWorkspaces(virtualWorkspaces func() []rootapiserver.NamedVirtualWorkspace) {
90+
p.virtualWorkspaces = virtualWorkspaces
91+
}

0 commit comments

Comments
 (0)