diff --git a/cmd/kcp/options/options.go b/cmd/kcp/options/options.go index b66b4749872..c839b292c46 100644 --- a/cmd/kcp/options/options.go +++ b/cmd/kcp/options/options.go @@ -22,14 +22,14 @@ import ( cliflag "k8s.io/component-base/cli/flag" kcpcoreoptions "github.com/kcp-dev/kcp/cmd/kcp-core/options" - serveroptions "github.com/kcp-dev/kcp/tmc/pkg/server/options" + tmcserveroptions "github.com/kcp-dev/kcp/tmc/pkg/server/options" ) type Options struct { Output io.Writer Generic kcpcoreoptions.GenericOptions - Server serveroptions.Options + Server tmcserveroptions.Options Extra ExtraOptions } @@ -39,7 +39,7 @@ func NewOptions(rootDir string) *Options { opts := &Options{ Output: nil, - Server: *serveroptions.NewOptions(rootDir), + Server: *tmcserveroptions.NewOptions(rootDir), Generic: *kcpcoreoptions.NewGeneric(rootDir), Extra: ExtraOptions{}, } @@ -51,7 +51,7 @@ type completedOptions struct { Output io.Writer Generic kcpcoreoptions.GenericOptions - Server serveroptions.CompletedOptions + Server tmcserveroptions.CompletedOptions Extra ExtraOptions } diff --git a/cmd/virtual-workspaces/command/cmd.go b/cmd/virtual-workspaces/command/cmd.go index d6291619057..12372e84218 100644 --- a/cmd/virtual-workspaces/command/cmd.go +++ b/cmd/virtual-workspaces/command/cmd.go @@ -45,6 +45,7 @@ import ( kcpfeatures "github.com/kcp-dev/kcp/pkg/features" "github.com/kcp-dev/kcp/pkg/server/bootstrap" virtualrootapiserver "github.com/kcp-dev/kcp/pkg/virtual/framework/rootapiserver" + corevwoptions "github.com/kcp-dev/kcp/pkg/virtual/options" ) func NewCommand(ctx context.Context, errout io.Writer) *cobra.Command { @@ -149,10 +150,6 @@ func Run(ctx context.Context, o *options.Options) error { } // create apiserver - virtualWorkspaces, err := o.VirtualWorkspaces.NewVirtualWorkspaces(identityConfig, o.RootPathPrefix, wildcardKubeInformers, wildcardKcpInformers, cacheKcpInformers) - if err != nil { - return err - } scheme := runtime.NewScheme() metav1.AddToGroupVersion(scheme, schema.GroupVersion{Group: "", Version: "v1"}) codecs := serializer.NewCodecFactory(scheme) @@ -163,23 +160,36 @@ func Run(ctx context.Context, o *options.Options) error { if err := o.Authentication.ApplyTo(&recommendedConfig.Authentication, recommendedConfig.SecureServing, recommendedConfig.OpenAPIConfig); err != nil { return err } - if err := o.Authorization.ApplyTo(&recommendedConfig.Config, virtualWorkspaces); err != nil { + if err := o.Audit.ApplyTo(&recommendedConfig.Config); err != nil { return err } - if err := o.Audit.ApplyTo(&recommendedConfig.Config); err != nil { + + rootAPIServerConfig, err := virtualrootapiserver.NewConfig(recommendedConfig) + if err != nil { + return err + } + + if err := o.Authorization.ApplyTo(&recommendedConfig.Config, func() []virtualrootapiserver.NamedVirtualWorkspace { + return rootAPIServerConfig.Extra.VirtualWorkspaces + }); err != nil { + return err + } + + coreVWs, err := o.CoreVirtualWorkspaces.NewVirtualWorkspaces(identityConfig, o.RootPathPrefix, wildcardKubeInformers, wildcardKcpInformers, cacheKcpInformers) + if err != nil { + return err + } + tmcVWs, err := o.TmcVirtualWorkspaces.NewVirtualWorkspaces(identityConfig, o.RootPathPrefix, cacheKcpInformers) + if err != nil { return err } - rootAPIServerConfig, err := virtualrootapiserver.NewRootAPIConfig(recommendedConfig, []virtualrootapiserver.InformerStart{ - wildcardKubeInformers.Start, - wildcardKcpInformers.Start, - cacheKcpInformers.Start, - }, virtualWorkspaces) + rootAPIServerConfig.Extra.VirtualWorkspaces, err = corevwoptions.Merge(coreVWs, tmcVWs) if err != nil { return err } completedRootAPIServerConfig := rootAPIServerConfig.Complete() - rootAPIServer, err := completedRootAPIServerConfig.New(genericapiserver.NewEmptyDelegate()) + rootAPIServer, err := virtualrootapiserver.NewServer(completedRootAPIServerConfig, genericapiserver.NewEmptyDelegate()) if err != nil { return err } @@ -190,8 +200,15 @@ func Run(ctx context.Context, o *options.Options) error { return err } - logger.Info("Starting virtual workspace apiserver on ", "externalAddress", rootAPIServerConfig.GenericConfig.ExternalAddress, "version", version.Get().String()) + logger.Info("Starting informers") + wildcardKubeInformers.Start(ctx.Done()) + wildcardKcpInformers.Start(ctx.Done()) + cacheKcpInformers.Start(ctx.Done()) + wildcardKubeInformers.WaitForCacheSync(ctx.Done()) + wildcardKcpInformers.WaitForCacheSync(ctx.Done()) + cacheKcpInformers.WaitForCacheSync(ctx.Done()) + logger.Info("Starting virtual workspace apiserver on ", "externalAddress", rootAPIServerConfig.Generic.ExternalAddress, "version", version.Get().String()) return preparedRootAPIServer.Run(ctx.Done()) } diff --git a/cmd/virtual-workspaces/options/options.go b/cmd/virtual-workspaces/options/options.go index cda1fd1eb92..6ba3dd09e46 100644 --- a/cmd/virtual-workspaces/options/options.go +++ b/cmd/virtual-workspaces/options/options.go @@ -30,7 +30,8 @@ import ( "k8s.io/component-base/logs" cacheoptions "github.com/kcp-dev/kcp/pkg/cache/client/options" - virtualworkspacesoptions "github.com/kcp-dev/kcp/pkg/virtual/options" + corevwoptions "github.com/kcp-dev/kcp/pkg/virtual/options" + tmcvwoptions "github.com/kcp-dev/kcp/tmc/pkg/virtual/options" ) // DefaultRootPathPrefix is basically constant forever, or we risk a breaking change. The @@ -48,13 +49,15 @@ type Options struct { Cache cacheoptions.Cache SecureServing genericapiserveroptions.SecureServingOptions Authentication genericapiserveroptions.DelegatingAuthenticationOptions - Authorization virtualworkspacesoptions.Authorization + Authorization corevwoptions.Authorization Audit genericapiserveroptions.AuditOptions Logs logs.Options - VirtualWorkspaces virtualworkspacesoptions.Options - ProfilerAddress string + CoreVirtualWorkspaces corevwoptions.Options + TmcVirtualWorkspaces tmcvwoptions.Options + + ProfilerAddress string } func NewOptions() *Options { @@ -66,12 +69,13 @@ func NewOptions() *Options { Cache: *cacheoptions.NewCache(), SecureServing: *genericapiserveroptions.NewSecureServingOptions(), Authentication: *genericapiserveroptions.NewDelegatingAuthenticationOptions(), - Authorization: *virtualworkspacesoptions.NewAuthorization(), + Authorization: *corevwoptions.NewAuthorization(), Audit: *genericapiserveroptions.NewAuditOptions(), Logs: *logs.NewOptions(), - VirtualWorkspaces: *virtualworkspacesoptions.NewOptions(), - ProfilerAddress: "", + CoreVirtualWorkspaces: *corevwoptions.NewOptions(), + TmcVirtualWorkspaces: *tmcvwoptions.NewOptions(), + ProfilerAddress: "", } opts.SecureServing.ServerCert.CertKey.CertFile = filepath.Join(".", ".kcp", "apiserver.crt") @@ -87,7 +91,8 @@ func (o *Options) AddFlags(flags *pflag.FlagSet) { o.Authentication.AddFlags(flags) o.Audit.AddFlags(flags) o.Logs.AddFlags(flags) - o.VirtualWorkspaces.AddFlags(flags) + o.CoreVirtualWorkspaces.AddFlags(flags) + o.TmcVirtualWorkspaces.AddFlags(flags) flags.StringVar(&o.KubeconfigFile, "kubeconfig", o.KubeconfigFile, "The kubeconfig file of the KCP instance that hosts workspaces.") @@ -102,7 +107,8 @@ func (o *Options) Validate() error { errs = append(errs, o.Cache.Validate()...) errs = append(errs, o.SecureServing.Validate()...) errs = append(errs, o.Authentication.Validate()...) - errs = append(errs, o.VirtualWorkspaces.Validate()...) + errs = append(errs, o.CoreVirtualWorkspaces.Validate()...) + errs = append(errs, o.TmcVirtualWorkspaces.Validate()...) if len(o.KubeconfigFile) == 0 { errs = append(errs, fmt.Errorf("--kubeconfig is required for this command")) diff --git a/pkg/reconciler/workload/synctarget/synctarget_reconcile.go b/pkg/reconciler/workload/synctarget/synctarget_reconcile.go index 8f2e29b2061..d4cc7e007fa 100644 --- a/pkg/reconciler/workload/synctarget/synctarget_reconcile.go +++ b/pkg/reconciler/workload/synctarget/synctarget_reconcile.go @@ -30,7 +30,7 @@ import ( virtualworkspacesoptions "github.com/kcp-dev/kcp/cmd/virtual-workspaces/options" corev1alpha1 "github.com/kcp-dev/kcp/pkg/apis/core/v1alpha1" workloadv1alpha1 "github.com/kcp-dev/kcp/pkg/apis/workload/v1alpha1" - syncerbuilder "github.com/kcp-dev/kcp/pkg/virtual/syncer/builder" + syncerbuilder "github.com/kcp-dev/kcp/tmc/pkg/virtual/syncer/builder" ) func (c *Controller) reconcile(ctx context.Context, syncTarget *workloadv1alpha1.SyncTarget, workspaceShards []*corev1alpha1.Shard) (*workloadv1alpha1.SyncTarget, error) { diff --git a/pkg/server/config.go b/pkg/server/config.go index fec9bce37bc..e8bce043645 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -74,10 +74,11 @@ type Config struct { EmbeddedEtcd *embeddedetcd.Config - GenericConfig *genericapiserver.Config // the config embedded into MiniAggregator, the head of the delegation chain - MiniAggregator *aggregator.MiniAggregatorConfig - Apis *apis.Config - ApiExtensions *apiextensionsapiserver.Config + GenericConfig *genericapiserver.Config // the config embedded into MiniAggregator, the head of the delegation chain + MiniAggregator *aggregator.MiniAggregatorConfig + Apis *apis.Config + ApiExtensions *apiextensionsapiserver.Config + OptionalVirtual *VirtualConfig ExtraConfig } @@ -129,11 +130,12 @@ type ExtraConfig struct { type completedConfig struct { Options kcpserveroptions.CompletedOptions - GenericConfig genericapiserver.CompletedConfig - EmbeddedEtcd embeddedetcd.CompletedConfig - MiniAggregator aggregator.CompletedMiniAggregatorConfig - Apis apis.CompletedConfig - ApiExtensions apiextensionsapiserver.CompletedConfig + GenericConfig genericapiserver.CompletedConfig + EmbeddedEtcd embeddedetcd.CompletedConfig + MiniAggregator aggregator.CompletedMiniAggregatorConfig + Apis apis.CompletedConfig + ApiExtensions apiextensionsapiserver.CompletedConfig + OptionalVirtual CompletedVirtualConfig ExtraConfig } @@ -145,14 +147,21 @@ type CompletedConfig struct { // Complete fills in any fields not set that are required to have valid data. It's mutating the receiver. func (c *Config) Complete() (CompletedConfig, error) { + miniAggregator := c.MiniAggregator.Complete() return CompletedConfig{&completedConfig{ Options: c.Options, GenericConfig: c.GenericConfig.Complete(informerfactoryhack.Wrap(c.KubeSharedInformerFactory)), EmbeddedEtcd: c.EmbeddedEtcd.Complete(), - MiniAggregator: c.MiniAggregator.Complete(), + MiniAggregator: miniAggregator, Apis: c.Apis.Complete(), ApiExtensions: c.ApiExtensions.Complete(), + OptionalVirtual: c.OptionalVirtual.Complete( + miniAggregator.GenericConfig.Authentication, + miniAggregator.GenericConfig.AuditPolicyRuleEvaluator, + miniAggregator.GenericConfig.AuditBackend, + c.GenericConfig.ExternalAddress, + ), ExtraConfig: c.ExtraConfig, }}, nil @@ -520,5 +529,21 @@ func NewConfig(opts kcpserveroptions.CompletedOptions) (*Config, error) { GenericConfig: c.GenericConfig, } + if opts.Virtual.Enabled { + virtualWorkspacesConfig := rest.CopyConfig(c.GenericConfig.LoopbackClientConfig) + virtualWorkspacesConfig = rest.AddUserAgent(virtualWorkspacesConfig, "virtual-workspaces") + + c.OptionalVirtual, err = newVirtualConfig( + opts, + virtualWorkspacesConfig, + c.KubeSharedInformerFactory, + c.KcpSharedInformerFactory, + c.CacheKcpSharedInformerFactory, + ) + if err != nil { + return nil, err + } + } + return c, nil } diff --git a/pkg/server/server.go b/pkg/server/server.go index 9493c2ae693..a29f26a46e5 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -44,6 +44,7 @@ import ( "github.com/kcp-dev/kcp/pkg/indexers" "github.com/kcp-dev/kcp/pkg/informer" metadataclient "github.com/kcp-dev/kcp/pkg/metadata" + virtualrootapiserver "github.com/kcp-dev/kcp/pkg/virtual/framework/rootapiserver" ) const resyncPeriod = 10 * time.Hour @@ -52,6 +53,7 @@ type Server struct { CompletedConfig *genericcontrolplane.ServerChain + virtual *virtualrootapiserver.Server syncedCh chan struct{} rootPhase1FinishedCh chan struct{} @@ -113,6 +115,19 @@ func NewServer(c CompletedConfig) (*Server, error) { return nil, err } + if c.Options.Virtual.Enabled { + s.virtual, err = c.OptionalVirtual.NewServer(s.preHandlerChainMux) + if err != nil { + return nil, err + } + if err := s.AddPostStartHook("kcp-start-virtual-workspaces", func(ctx genericapiserver.PostStartHookContext) error { + s.virtual.GenericAPIServer.RunPostStartHooks(ctx.StopCh) + return nil + }); err != nil { + return nil, err + } + } + return s, nil } @@ -472,14 +487,6 @@ func (s *Server) Run(ctx context.Context) error { } } - if s.Options.Virtual.Enabled { - virtualWorkspacesConfig := rest.CopyConfig(s.GenericConfig.LoopbackClientConfig) - virtualWorkspacesConfig = rest.AddUserAgent(virtualWorkspacesConfig, "virtual-workspaces") - if err := s.installVirtualWorkspaces(ctx, virtualWorkspacesConfig, s.GenericConfig.Authentication, s.GenericConfig.ExternalAddress, s.GenericConfig.AuditPolicyRuleEvaluator, s.preHandlerChainMux); err != nil { - return err - } - } - if len(s.Options.Cache.Client.KubeconfigFile) == 0 { if err := s.installCacheServer(ctx); err != nil { return err diff --git a/pkg/server/virtual.go b/pkg/server/virtual.go index e0d78ea6192..a86c811e6a9 100644 --- a/pkg/server/virtual.go +++ b/pkg/server/virtual.go @@ -17,9 +17,10 @@ limitations under the License. package server import ( - "context" "net/http" + kcpkubernetesinformers "github.com/kcp-dev/client-go/informers" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -28,9 +29,10 @@ import ( genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/apiserver/pkg/server/healthz" "k8s.io/client-go/rest" - "k8s.io/klog/v2" virtualcommandoptions "github.com/kcp-dev/kcp/cmd/virtual-workspaces/options" + kcpinformers "github.com/kcp-dev/kcp/pkg/client/informers/externalversions" + kcpserveroptions "github.com/kcp-dev/kcp/pkg/server/options" virtualrootapiserver "github.com/kcp-dev/kcp/pkg/virtual/framework/rootapiserver" virtualoptions "github.com/kcp-dev/kcp/pkg/virtual/options" ) @@ -39,31 +41,25 @@ type mux interface { Handle(pattern string, handler http.Handler) } -func (s *Server) installVirtualWorkspaces( - ctx context.Context, - config *rest.Config, - auth genericapiserver.AuthenticationInfo, - externalAddress string, - auditEvaluator kaudit.PolicyRuleEvaluator, - preHandlerChainMux mux, -) error { - logger := klog.FromContext(ctx) - // create virtual workspaces - virtualWorkspaces, err := s.Options.Virtual.VirtualWorkspaces.NewVirtualWorkspaces( - config, - virtualcommandoptions.DefaultRootPathPrefix, - s.KubeSharedInformerFactory, - s.KcpSharedInformerFactory, - s.CacheKcpSharedInformerFactory, - ) - if err != nil { - return err - } +type VirtualConfig virtualrootapiserver.Config - // create apiserver, with its own delegation chain +type completedVirtualConfig struct { + virtualrootapiserver.CompletedConfig +} + +type CompletedVirtualConfig struct { + // Embed a private pointer that cannot be instantiated outside of this package. + *completedVirtualConfig +} + +func newVirtualConfig( + o kcpserveroptions.CompletedOptions, + config *rest.Config, + kubeSharedInformerFactory kcpkubernetesinformers.SharedInformerFactory, + kcpSharedInformerFactory, cacheKcpSharedInformerFactory kcpinformers.SharedInformerFactory, +) (*VirtualConfig, error) { scheme := runtime.NewScheme() metav1.AddToGroupVersion(scheme, schema.GroupVersion{Group: "", Version: "v1"}) - codecs := serializer.NewCodecFactory(scheme) recommendedConfig := genericapiserver.NewRecommendedConfig(codecs) @@ -73,50 +69,67 @@ func (s *Server) installVirtualWorkspaces( recommendedConfig.HealthzChecks = []healthz.HealthChecker{} recommendedConfig.ReadyzChecks = []healthz.HealthChecker{} recommendedConfig.LivezChecks = []healthz.HealthChecker{} - recommendedConfig.Authentication = auth + + c, err := virtualrootapiserver.NewConfig(recommendedConfig) + if err != nil { + return nil, err + } authorizationOptions := virtualoptions.NewAuthorization() - authorizationOptions.AlwaysAllowGroups = s.Options.Authorization.AlwaysAllowGroups - authorizationOptions.AlwaysAllowPaths = s.Options.Authorization.AlwaysAllowPaths - if err := authorizationOptions.ApplyTo(&recommendedConfig.Config, virtualWorkspaces); err != nil { - return err + authorizationOptions.AlwaysAllowGroups = o.Authorization.AlwaysAllowGroups + authorizationOptions.AlwaysAllowPaths = o.Authorization.AlwaysAllowPaths + if err := authorizationOptions.ApplyTo(&recommendedConfig.Config, func() []virtualrootapiserver.NamedVirtualWorkspace { + return c.Extra.VirtualWorkspaces + }); err != nil { + return nil, err } - rootAPIServerConfig, err := virtualrootapiserver.NewRootAPIConfig(recommendedConfig, nil, virtualWorkspaces) + c.Extra.VirtualWorkspaces, err = o.Virtual.VirtualWorkspaces.NewVirtualWorkspaces( + config, + virtualcommandoptions.DefaultRootPathPrefix, + kubeSharedInformerFactory, + kcpSharedInformerFactory, + cacheKcpSharedInformerFactory, + ) if err != nil { - return err + return nil, err } - rootAPIServerConfig.GenericConfig.ExternalAddress = externalAddress - completedRootAPIServerConfig := rootAPIServerConfig.Complete() - completedRootAPIServerConfig.GenericConfig.AuditBackend = s.MiniAggregator.GenericAPIServer.AuditBackend - completedRootAPIServerConfig.GenericConfig.AuditPolicyRuleEvaluator = auditEvaluator + return (*VirtualConfig)(c), nil +} - rootAPIServer, err := completedRootAPIServerConfig.New(genericapiserver.NewEmptyDelegate()) - if err != nil { - return err +func (c *VirtualConfig) Complete(auth genericapiserver.AuthenticationInfo, auditEvaluator kaudit.PolicyRuleEvaluator, auditBackend kaudit.Backend, externalAddress string) CompletedVirtualConfig { + if c == nil { + return CompletedVirtualConfig{} } - if err := s.MiniAggregator.GenericAPIServer.AddReadyzChecks(completedRootAPIServerConfig.GenericConfig.ReadyzChecks...); err != nil { - return err + c.Generic.Authentication = auth + c.Generic.ExternalAddress = externalAddress + + completed := &completedVirtualConfig{ + (*virtualrootapiserver.Config)(c).Complete(), } - preparedRootAPIServer := rootAPIServer.GenericAPIServer.PrepareRun() + completed.Generic.AuditBackend = auditBackend + completed.Generic.AuditPolicyRuleEvaluator = auditEvaluator - // this **must** be done after PrepareRun() as it sets up the openapi endpoints - if err := completedRootAPIServerConfig.WithOpenAPIAggregationController(preparedRootAPIServer.GenericAPIServer); err != nil { - return err + return CompletedVirtualConfig{completed} +} + +func (c CompletedVirtualConfig) NewServer(preHandlerChainMux mux) (*virtualrootapiserver.Server, error) { + s, err := virtualrootapiserver.NewServer(c.CompletedConfig, genericapiserver.NewEmptyDelegate()) + if err != nil { + return nil, err } - if err := s.AddPostStartHook("kcp-start-virtual-workspace", func(ctx genericapiserver.PostStartHookContext) error { - preparedRootAPIServer.RunPostStartHooks(ctx.StopCh) - return nil - }); err != nil { - return err + preparedRootAPIServer := s.GenericAPIServer.PrepareRun() + + // this **must** be done after PrepareRun() as it sets up the openapi endpoints + if err := c.WithOpenAPIAggregationController(preparedRootAPIServer.GenericAPIServer); err != nil { + return nil, err } - logger.Info("starting virtual workspace apiserver") preHandlerChainMux.Handle(virtualcommandoptions.DefaultRootPathPrefix+"/", preparedRootAPIServer.GenericAPIServer.Handler) - return nil + return s, nil } diff --git a/pkg/virtual/framework/authorization/authorizer.go b/pkg/virtual/framework/authorization/authorizer.go index 2ee5977713e..a723eb61f08 100644 --- a/pkg/virtual/framework/authorization/authorizer.go +++ b/pkg/virtual/framework/authorization/authorizer.go @@ -26,7 +26,7 @@ import ( "github.com/kcp-dev/kcp/pkg/virtual/framework/rootapiserver" ) -func NewVirtualWorkspaceAuthorizer(virtualWorkspaces []rootapiserver.NamedVirtualWorkspace) authorizer.Authorizer { +func NewVirtualWorkspaceAuthorizer(virtualWorkspaces func() []rootapiserver.NamedVirtualWorkspace) authorizer.Authorizer { return &virtualWorkspaceAuthorizer{ virtualWorkspaces: virtualWorkspaces, } @@ -35,7 +35,7 @@ func NewVirtualWorkspaceAuthorizer(virtualWorkspaces []rootapiserver.NamedVirtua var _ authorizer.Authorizer = (*virtualWorkspaceAuthorizer)(nil) type virtualWorkspaceAuthorizer struct { - virtualWorkspaces []rootapiserver.NamedVirtualWorkspace + virtualWorkspaces func() []rootapiserver.NamedVirtualWorkspace } func (a *virtualWorkspaceAuthorizer) Authorize(ctx context.Context, attrs authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { @@ -44,7 +44,7 @@ func (a *virtualWorkspaceAuthorizer) Authorize(ctx context.Context, attrs author return authorizer.DecisionNoOpinion, "Path not resolved to a valid virtual workspace", nil } - for _, vw := range a.virtualWorkspaces { + for _, vw := range a.virtualWorkspaces() { if vw.Name == virtualWorkspaceName { return vw.VirtualWorkspace.Authorize(ctx, attrs) } diff --git a/pkg/virtual/framework/rootapiserver/config.go b/pkg/virtual/framework/rootapiserver/config.go new file mode 100644 index 00000000000..5537392628f --- /dev/null +++ b/pkg/virtual/framework/rootapiserver/config.go @@ -0,0 +1,84 @@ +/* +Copyright 2023 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rootapiserver + +import ( + genericapiserver "k8s.io/apiserver/pkg/server" + "k8s.io/client-go/rest" + + "github.com/kcp-dev/kcp/pkg/virtual/framework" +) + +type NamedVirtualWorkspace struct { + Name string + framework.VirtualWorkspace +} + +type Config struct { + Generic *genericapiserver.RecommendedConfig + Extra ExtraConfig +} + +type ExtraConfig struct { + VirtualWorkspaces []NamedVirtualWorkspace +} + +type completedConfig struct { + Generic genericapiserver.CompletedConfig + Extra *ExtraConfig +} + +type CompletedConfig struct { + // Embed a private pointer that cannot be instantiated outside of this package. + *completedConfig +} + +// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver. +func (c *Config) Complete() CompletedConfig { + if c == nil { + return CompletedConfig{} + } + + cfg := completedConfig{ + c.Generic.Complete(), + &c.Extra, + } + + return CompletedConfig{&cfg} +} + +func (c *completedConfig) WithOpenAPIAggregationController(delegatedAPIServer *genericapiserver.GenericAPIServer) error { + return nil +} + +func NewConfig(recommendedConfig *genericapiserver.RecommendedConfig) (*Config, error) { + // Loopback is not wired for now, since virtual workspaces are expected to delegate to + // some APIServer. + // The RootAPISrver is just a proxy to the various virtual workspaces. + // We might consider a giving a special meaning to a global loopback config, in the future + // but that's not the case for now. + recommendedConfig.Config.LoopbackClientConfig = &rest.Config{ + Host: "loopback-config-not-wired-for-now", + } + + ret := &Config{ + Generic: recommendedConfig, + Extra: ExtraConfig{}, + } + + return ret, nil +} diff --git a/pkg/virtual/framework/rootapiserver/health.go b/pkg/virtual/framework/rootapiserver/health.go new file mode 100644 index 00000000000..b6af78254b6 --- /dev/null +++ b/pkg/virtual/framework/rootapiserver/health.go @@ -0,0 +1,46 @@ +/* +Copyright 2023 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rootapiserver + +import ( + "net/http" + + "k8s.io/apiserver/pkg/server/healthz" + + "github.com/kcp-dev/kcp/pkg/virtual/framework" +) + +type asHealthCheck struct { + name string + framework.VirtualWorkspace +} + +func (vw asHealthCheck) Name() string { + return vw.name +} + +func (vw asHealthCheck) Check(req *http.Request) error { + return vw.IsReady() +} + +func asHealthChecks(workspaces []NamedVirtualWorkspace) []healthz.HealthChecker { + healthCheckers := make([]healthz.HealthChecker, 0, len(workspaces)) + for _, vw := range workspaces { + healthCheckers = append(healthCheckers, asHealthCheck{name: vw.Name, VirtualWorkspace: vw}) + } + return healthCheckers +} diff --git a/pkg/virtual/framework/rootapiserver/root_apiserver.go b/pkg/virtual/framework/rootapiserver/root_apiserver.go deleted file mode 100644 index 05ae3263c02..00000000000 --- a/pkg/virtual/framework/rootapiserver/root_apiserver.go +++ /dev/null @@ -1,230 +0,0 @@ -/* -Copyright 2021 The KCP Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package rootapiserver - -import ( - "fmt" - "net/http" - "net/url" - "strings" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/apiserver/pkg/server/healthz" - "k8s.io/apiserver/pkg/warning" - "k8s.io/client-go/rest" - componentbaseversion "k8s.io/component-base/version" - - "github.com/kcp-dev/kcp/pkg/virtual/framework" - virtualcontext "github.com/kcp-dev/kcp/pkg/virtual/framework/context" -) - -var ( - errorScheme = runtime.NewScheme() - errorCodecs = serializer.NewCodecFactory(errorScheme) -) - -func init() { - errorScheme.AddUnversionedTypes(metav1.Unversioned, - &metav1.Status{}, - ) -} - -type InformerStart func(stopCh <-chan struct{}) - -type RootAPIExtraConfig struct { - // we phrase it like this so we can build the post-start-hook, but no one can take more indirect dependencies on informers - informerStart func(stopCh <-chan struct{}) - - VirtualWorkspaces []NamedVirtualWorkspace -} - -type NamedVirtualWorkspace struct { - Name string - framework.VirtualWorkspace -} - -// Validate helps ensure that we build this config correctly, because there are lots of bits to remember for now. -func (c *RootAPIExtraConfig) Validate() error { - ret := []error{} - - return utilerrors.NewAggregate(ret) -} - -type RootAPIConfig struct { - GenericConfig *genericapiserver.RecommendedConfig - ExtraConfig RootAPIExtraConfig -} - -// RootAPIServer is only responsible for serving the APIs for the virtual workspace -// at a given root path or root path family -// It does NOT expose oauth, related oauth endpoints, or any kube APIs. -type RootAPIServer struct { - GenericAPIServer *genericapiserver.GenericAPIServer -} - -type completedConfig struct { - GenericConfig genericapiserver.CompletedConfig - ExtraConfig *RootAPIExtraConfig -} - -type CompletedConfig struct { - // Embed a private pointer that cannot be instantiated outside of this package. - *completedConfig -} - -// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver. -func (c *RootAPIConfig) Complete() completedConfig { - cfg := completedConfig{ - c.GenericConfig.Complete(), - &c.ExtraConfig, - } - - return cfg -} - -func (c *completedConfig) WithOpenAPIAggregationController(delegatedAPIServer *genericapiserver.GenericAPIServer) error { - return nil -} - -func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*RootAPIServer, error) { - delegateAPIServer := delegationTarget - for _, vw := range c.ExtraConfig.VirtualWorkspaces { - var err error - delegateAPIServer, err = vw.Register(vw.Name, c.GenericConfig, delegateAPIServer) - if err != nil { - return nil, err - } - } - - c.GenericConfig.BuildHandlerChainFunc = c.getRootHandlerChain(delegateAPIServer) - c.GenericConfig.ReadyzChecks = append(c.GenericConfig.ReadyzChecks, asHealthChecks(c.ExtraConfig.VirtualWorkspaces)...) - - genericServer, err := c.GenericConfig.New("virtual-workspaces-root-apiserver", delegateAPIServer) - if err != nil { - return nil, err - } - - s := &RootAPIServer{ - GenericAPIServer: genericServer, - } - - // register our poststarthooks - s.GenericAPIServer.AddPostStartHookOrDie("virtual-workspace-startinformers", func(context genericapiserver.PostStartHookContext) error { - c.ExtraConfig.informerStart(context.StopCh) - return nil - }) - - return s, nil -} - -type asHealthCheck struct { - name string - framework.VirtualWorkspace -} - -func (vw asHealthCheck) Name() string { - return vw.name -} - -func (vw asHealthCheck) Check(req *http.Request) error { - return vw.IsReady() -} - -func asHealthChecks(workspaces []NamedVirtualWorkspace) []healthz.HealthChecker { - healthCheckers := make([]healthz.HealthChecker, 0, len(workspaces)) - for _, vw := range workspaces { - healthCheckers = append(healthCheckers, asHealthCheck{name: vw.Name, VirtualWorkspace: vw}) - } - return healthCheckers -} - -func (c completedConfig) getRootHandlerChain(delegateAPIServer genericapiserver.DelegationTarget) func(http.Handler, *genericapiserver.Config) http.Handler { - return func(apiHandler http.Handler, genericConfig *genericapiserver.Config) http.Handler { - delegateAfterDefaultHandlerChain := genericapiserver.DefaultBuildHandlerChain( - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if _, virtualWorkspaceNameExists := virtualcontext.VirtualWorkspaceNameFrom(req.Context()); virtualWorkspaceNameExists { - delegatedHandler := delegateAPIServer.UnprotectedHandler() - if delegatedHandler != nil { - delegatedHandler.ServeHTTP(w, req) - } - return - } - apiHandler.ServeHTTP(w, req) - }), c.GenericConfig.Config) - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - requestContext := req.Context() - // detect old kubectl plugins and inject warning headers - if req.UserAgent() == "Go-http-client/2.0" { - // TODO(sttts): in the future compare the plugin version to the server version and warn outside of skew compatibility guarantees. - warning.AddWarning(requestContext, "", - fmt.Sprintf("You are using an old kubectl-kcp plugin. Please update to a version matching the kcp server version %q.", componentbaseversion.Get().GitVersion)) - } - - for _, vw := range c.ExtraConfig.VirtualWorkspaces { - if accepted, prefixToStrip, completedContext := vw.ResolveRootPath(req.URL.Path, requestContext); accepted { - req.URL.Path = strings.TrimPrefix(req.URL.Path, prefixToStrip) - newURL, err := url.Parse(req.URL.String()) - if err != nil { - responsewriters.ErrorNegotiated( - apierrors.NewInternalError(fmt.Errorf("unable to resolve %s, err %w", req.URL.Path, err)), - errorCodecs, schema.GroupVersion{}, - w, req) - return - } - req.URL = newURL - req = req.WithContext(virtualcontext.WithVirtualWorkspaceName(completedContext, vw.Name)) - break - } - } - delegateAfterDefaultHandlerChain.ServeHTTP(w, req) - }) - } -} - -func NewRootAPIConfig(recommendedConfig *genericapiserver.RecommendedConfig, informerStarts []InformerStart, virtualWorkspaces []NamedVirtualWorkspace) (*RootAPIConfig, error) { - // TODO: genericConfig.ExternalAddress = ... allow a command line flag or it to be overridden by a top-level multiroot apiServer - - // Loopback is not wired for now, since virtual workspaces are expected to delegate to - // some APIServer. - // The RootAPISrver is just a proxy to the various virtual workspaces. - // We might consider a giving a special meaning to a global loopback config, in the future - // but that's not the case for now. - recommendedConfig.Config.LoopbackClientConfig = &rest.Config{ - Host: "loopback-config-not-wired-for-now", - } - - ret := &RootAPIConfig{ - GenericConfig: recommendedConfig, - ExtraConfig: RootAPIExtraConfig{ - informerStart: func(stopCh <-chan struct{}) { - for _, informerStart := range informerStarts { - informerStart(stopCh) - } - }, - VirtualWorkspaces: virtualWorkspaces, - }, - } - - return ret, ret.ExtraConfig.Validate() -} diff --git a/pkg/virtual/framework/rootapiserver/server.go b/pkg/virtual/framework/rootapiserver/server.go new file mode 100644 index 00000000000..a291a72095f --- /dev/null +++ b/pkg/virtual/framework/rootapiserver/server.go @@ -0,0 +1,122 @@ +/* +Copyright 2023 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rootapiserver + +import ( + "fmt" + "net/http" + "net/url" + "strings" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" + genericapiserver "k8s.io/apiserver/pkg/server" + "k8s.io/apiserver/pkg/warning" + componentbaseversion "k8s.io/component-base/version" + + virtualcontext "github.com/kcp-dev/kcp/pkg/virtual/framework/context" +) + +var ( + errorScheme = runtime.NewScheme() + errorCodecs = serializer.NewCodecFactory(errorScheme) +) + +func init() { + errorScheme.AddUnversionedTypes(metav1.Unversioned, + &metav1.Status{}, + ) +} + +// Server is only responsible for serving the APIs for the virtual workspace +// at a given root path or root path family +// It does NOT expose oauth, related oauth endpoints, or any kube APIs. +type Server struct { + GenericAPIServer *genericapiserver.GenericAPIServer +} + +func NewServer(c CompletedConfig, delegationTarget genericapiserver.DelegationTarget) (*Server, error) { + delegateAPIServer := delegationTarget + for _, vw := range c.Extra.VirtualWorkspaces { + var err error + delegateAPIServer, err = vw.Register(vw.Name, c.Generic, delegateAPIServer) + if err != nil { + return nil, err + } + } + + c.Generic.BuildHandlerChainFunc = getRootHandlerChain(c, delegateAPIServer) + c.Generic.ReadyzChecks = append(c.Generic.ReadyzChecks, asHealthChecks(c.Extra.VirtualWorkspaces)...) + + genericServer, err := c.Generic.New("virtual-workspaces-root-apiserver", delegateAPIServer) + if err != nil { + return nil, err + } + + s := &Server{ + GenericAPIServer: genericServer, + } + + return s, nil +} + +func getRootHandlerChain(c CompletedConfig, delegateAPIServer genericapiserver.DelegationTarget) func(http.Handler, *genericapiserver.Config) http.Handler { + return func(apiHandler http.Handler, genericConfig *genericapiserver.Config) http.Handler { + delegateAfterDefaultHandlerChain := genericapiserver.DefaultBuildHandlerChain( + http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if _, virtualWorkspaceNameExists := virtualcontext.VirtualWorkspaceNameFrom(req.Context()); virtualWorkspaceNameExists { + delegatedHandler := delegateAPIServer.UnprotectedHandler() + if delegatedHandler != nil { + delegatedHandler.ServeHTTP(w, req) + } + return + } + apiHandler.ServeHTTP(w, req) + }), c.Generic.Config) + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + requestContext := req.Context() + // detect old kubectl plugins and inject warning headers + if req.UserAgent() == "Go-http-client/2.0" { + // TODO(sttts): in the future compare the plugin version to the server version and warn outside of skew compatibility guarantees. + warning.AddWarning(requestContext, "", + fmt.Sprintf("You are using an old kubectl-kcp plugin. Please update to a version matching the kcp server version %q.", componentbaseversion.Get().GitVersion)) + } + + for _, vw := range c.Extra.VirtualWorkspaces { + if accepted, prefixToStrip, completedContext := vw.ResolveRootPath(req.URL.Path, requestContext); accepted { + req.URL.Path = strings.TrimPrefix(req.URL.Path, prefixToStrip) + newURL, err := url.Parse(req.URL.String()) + if err != nil { + responsewriters.ErrorNegotiated( + apierrors.NewInternalError(fmt.Errorf("unable to resolve %s, err %w", req.URL.Path, err)), + errorCodecs, schema.GroupVersion{}, + w, req) + return + } + req.URL = newURL + req = req.WithContext(virtualcontext.WithVirtualWorkspaceName(completedContext, vw.Name)) + break + } + } + delegateAfterDefaultHandlerChain.ServeHTTP(w, req) + }) + } +} diff --git a/pkg/virtual/options/authorization.go b/pkg/virtual/options/authorization.go index 4a8fe154b63..9dd4289fad1 100644 --- a/pkg/virtual/options/authorization.go +++ b/pkg/virtual/options/authorization.go @@ -80,7 +80,7 @@ func (s *Authorization) AddFlags(fs *pflag.FlagSet) { "contacting the 'core' kubernetes server.") } -func (s *Authorization) ApplyTo(config *genericapiserver.Config, virtualWorkspaces []rootapiserver.NamedVirtualWorkspace) error { +func (s *Authorization) ApplyTo(config *genericapiserver.Config, virtualWorkspaces func() []rootapiserver.NamedVirtualWorkspace) error { var authorizers []authorizer.Authorizer // group authorizer diff --git a/pkg/virtual/options/options.go b/pkg/virtual/options/options.go index ce1951839f6..d87a09d2ada 100644 --- a/pkg/virtual/options/options.go +++ b/pkg/virtual/options/options.go @@ -28,20 +28,17 @@ import ( apiexportoptions "github.com/kcp-dev/kcp/pkg/virtual/apiexport/options" "github.com/kcp-dev/kcp/pkg/virtual/framework/rootapiserver" initializingworkspacesoptions "github.com/kcp-dev/kcp/pkg/virtual/initializingworkspaces/options" - synceroptions "github.com/kcp-dev/kcp/pkg/virtual/syncer/options" ) const virtualWorkspacesFlagPrefix = "virtual-workspaces-" type Options struct { - Syncer *synceroptions.Syncer APIExport *apiexportoptions.APIExport InitializingWorkspaces *initializingworkspacesoptions.InitializingWorkspaces } func NewOptions() *Options { return &Options{ - Syncer: synceroptions.New(), APIExport: apiexportoptions.New(), InitializingWorkspaces: initializingworkspacesoptions.New(), } @@ -50,7 +47,6 @@ func NewOptions() *Options { func (o *Options) Validate() []error { var errs []error - errs = append(errs, o.Syncer.Validate(virtualWorkspacesFlagPrefix)...) errs = append(errs, o.APIExport.Validate(virtualWorkspacesFlagPrefix)...) errs = append(errs, o.InitializingWorkspaces.Validate(virtualWorkspacesFlagPrefix)...) @@ -67,11 +63,6 @@ func (o *Options) NewVirtualWorkspaces( wildcardKubeInformers kcpkubernetesinformers.SharedInformerFactory, wildcardKcpInformers, cachedKcpInformers kcpinformers.SharedInformerFactory, ) ([]rootapiserver.NamedVirtualWorkspace, error) { - syncer, err := o.Syncer.NewVirtualWorkspaces(rootPathPrefix, config, cachedKcpInformers) - if err != nil { - return nil, err - } - apiexports, err := o.APIExport.NewVirtualWorkspaces(rootPathPrefix, config, cachedKcpInformers) if err != nil { return nil, err @@ -82,14 +73,14 @@ func (o *Options) NewVirtualWorkspaces( return nil, err } - all, err := merge(syncer, apiexports, initializingworkspaces) + all, err := Merge(apiexports, initializingworkspaces) if err != nil { return nil, err } return all, nil } -func merge(sets ...[]rootapiserver.NamedVirtualWorkspace) ([]rootapiserver.NamedVirtualWorkspace, error) { +func Merge(sets ...[]rootapiserver.NamedVirtualWorkspace) ([]rootapiserver.NamedVirtualWorkspace, error) { var workspaces []rootapiserver.NamedVirtualWorkspace seen := map[string]bool{} for _, set := range sets { diff --git a/tmc/pkg/coordination/helpers.go b/tmc/pkg/coordination/helpers.go index 0bb7fd74921..fac137a7b3a 100644 --- a/tmc/pkg/coordination/helpers.go +++ b/tmc/pkg/coordination/helpers.go @@ -27,8 +27,8 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "github.com/kcp-dev/kcp/pkg/apis/workload/v1alpha1" - syncercontext "github.com/kcp-dev/kcp/pkg/virtual/syncer/context" - "github.com/kcp-dev/kcp/pkg/virtual/syncer/transformations" + syncercontext "github.com/kcp-dev/kcp/tmc/pkg/virtual/syncer/context" + "github.com/kcp-dev/kcp/tmc/pkg/virtual/syncer/transformations" ) // FilteredSyncerViewsChanged returns true if the syncer view fields changed between old and new diff --git a/tmc/pkg/server/config.go b/tmc/pkg/server/config.go index 9746dc738a6..814670cfff3 100644 --- a/tmc/pkg/server/config.go +++ b/tmc/pkg/server/config.go @@ -19,7 +19,11 @@ package server import ( _ "net/http/pprof" + "k8s.io/client-go/rest" + + virtualcommandoptions "github.com/kcp-dev/kcp/cmd/virtual-workspaces/options" coreserver "github.com/kcp-dev/kcp/pkg/server" + corevwoptions "github.com/kcp-dev/kcp/pkg/virtual/options" "github.com/kcp-dev/kcp/tmc/pkg/server/options" ) @@ -67,6 +71,21 @@ func NewConfig(opts options.CompletedOptions) (*Config, error) { return nil, err } + // add tmc virtual workspaces + if opts.Core.Virtual.Enabled { + virtualWorkspacesConfig := rest.CopyConfig(core.GenericConfig.LoopbackClientConfig) + virtualWorkspacesConfig = rest.AddUserAgent(virtualWorkspacesConfig, "virtual-workspaces") + + tmcVWs, err := opts.TmcVirtualWorkspaces.NewVirtualWorkspaces(virtualWorkspacesConfig, virtualcommandoptions.DefaultRootPathPrefix, core.CacheKcpSharedInformerFactory) + if err != nil { + return nil, err + } + core.OptionalVirtual.Extra.VirtualWorkspaces, err = corevwoptions.Merge(core.OptionalVirtual.Extra.VirtualWorkspaces, tmcVWs) + if err != nil { + return nil, err + } + } + c := &Config{ Options: opts, Core: core, diff --git a/tmc/pkg/server/options/controllers.go b/tmc/pkg/server/options/controllers.go index 34c4d982ce7..a8130895cb4 100644 --- a/tmc/pkg/server/options/controllers.go +++ b/tmc/pkg/server/options/controllers.go @@ -31,7 +31,7 @@ type Controllers struct { type ApiResourceController = apiresource.Options type SyncTargetHeartbeatController = heartbeat.Options -func NewControllers() *Controllers { +func NewTmcControllers() *Controllers { return &Controllers{ ApiResource: *apiresource.NewOptions(), SyncTargetHeartbeat: *heartbeat.NewOptions(), diff --git a/tmc/pkg/server/options/options.go b/tmc/pkg/server/options/options.go index 0245da2047d..b0c8451a795 100644 --- a/tmc/pkg/server/options/options.go +++ b/tmc/pkg/server/options/options.go @@ -20,11 +20,13 @@ import ( cliflag "k8s.io/component-base/cli/flag" kcpcoreoptions "github.com/kcp-dev/kcp/pkg/server/options" + tmcvirtualoptions "github.com/kcp-dev/kcp/tmc/pkg/virtual/options" ) type Options struct { - Core kcpcoreoptions.Options - Controllers Controllers + Core kcpcoreoptions.Options + TmcControllers Controllers + TmcVirtualWorkspaces tmcvirtualoptions.Options Extra ExtraOptions } @@ -33,8 +35,9 @@ type ExtraOptions struct { } type completedOptions struct { - Core kcpcoreoptions.CompletedOptions - Controllers Controllers + Core kcpcoreoptions.CompletedOptions + Controllers Controllers + TmcVirtualWorkspaces tmcvirtualoptions.Options Extra ExtraOptions } @@ -46,8 +49,9 @@ type CompletedOptions struct { // NewOptions creates a new Options with default parameters. func NewOptions(rootDir string) *Options { o := &Options{ - Core: *kcpcoreoptions.NewOptions(rootDir), - Controllers: *NewControllers(), + Core: *kcpcoreoptions.NewOptions(rootDir), + TmcControllers: *NewTmcControllers(), + TmcVirtualWorkspaces: *tmcvirtualoptions.NewOptions(), Extra: ExtraOptions{}, } @@ -57,7 +61,8 @@ func NewOptions(rootDir string) *Options { func (o *Options) AddFlags(fss *cliflag.NamedFlagSets) { o.Core.AddFlags(fss) - o.Controllers.AddFlags(fss.FlagSet("KCP Controllers")) + o.TmcControllers.AddFlags(fss.FlagSet("KCP Controllers")) + o.TmcVirtualWorkspaces.AddFlags(fss.FlagSet("KCP Virtual Workspaces")) } func (o *CompletedOptions) Validate() []error { @@ -74,15 +79,16 @@ func (o *Options) Complete(rootDir string) (*CompletedOptions, error) { if err != nil { return nil, err } - if err := o.Controllers.Complete(rootDir); err != nil { + if err := o.TmcControllers.Complete(rootDir); err != nil { return nil, err } return &CompletedOptions{ completedOptions: &completedOptions{ - Core: *core, - Controllers: o.Controllers, - Extra: o.Extra, + Core: *core, + Controllers: o.TmcControllers, + TmcVirtualWorkspaces: o.TmcVirtualWorkspaces, + Extra: o.Extra, }, }, nil } diff --git a/tmc/pkg/virtual/options/options.go b/tmc/pkg/virtual/options/options.go new file mode 100644 index 00000000000..451fc1fa80c --- /dev/null +++ b/tmc/pkg/virtual/options/options.go @@ -0,0 +1,68 @@ +/* +Copyright 2022 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package options + +import ( + "github.com/spf13/pflag" + + "k8s.io/client-go/rest" + + kcpinformers "github.com/kcp-dev/kcp/pkg/client/informers/externalversions" + "github.com/kcp-dev/kcp/pkg/virtual/framework/rootapiserver" + "github.com/kcp-dev/kcp/pkg/virtual/options" + synceroptions "github.com/kcp-dev/kcp/tmc/pkg/virtual/syncer/options" +) + +const virtualWorkspacesFlagPrefix = "virtual-workspaces-" + +type Options struct { + Syncer *synceroptions.Syncer +} + +func NewOptions() *Options { + return &Options{ + Syncer: synceroptions.New(), + } +} + +func (o *Options) Validate() []error { + var errs []error + + errs = append(errs, o.Syncer.Validate(virtualWorkspacesFlagPrefix)...) + + return errs +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { +} + +func (o *Options) NewVirtualWorkspaces( + config *rest.Config, + rootPathPrefix string, + cachedKcpInformers kcpinformers.SharedInformerFactory, +) ([]rootapiserver.NamedVirtualWorkspace, error) { + syncer, err := o.Syncer.NewVirtualWorkspaces(rootPathPrefix, config, cachedKcpInformers) + if err != nil { + return nil, err + } + + all, err := options.Merge(syncer) + if err != nil { + return nil, err + } + return all, nil +} diff --git a/pkg/virtual/syncer/builder/build.go b/tmc/pkg/virtual/syncer/builder/build.go similarity index 95% rename from pkg/virtual/syncer/builder/build.go rename to tmc/pkg/virtual/syncer/builder/build.go index 2f0203c2dc8..94d3918745e 100644 --- a/pkg/virtual/syncer/builder/build.go +++ b/tmc/pkg/virtual/syncer/builder/build.go @@ -30,9 +30,9 @@ import ( "github.com/kcp-dev/kcp/pkg/indexers" "github.com/kcp-dev/kcp/pkg/virtual/framework/forwardingregistry" "github.com/kcp-dev/kcp/pkg/virtual/framework/rootapiserver" - "github.com/kcp-dev/kcp/pkg/virtual/syncer/controllers/apireconciler" - "github.com/kcp-dev/kcp/pkg/virtual/syncer/transformations" - "github.com/kcp-dev/kcp/pkg/virtual/syncer/upsyncer" + "github.com/kcp-dev/kcp/tmc/pkg/virtual/syncer/controllers/apireconciler" + "github.com/kcp-dev/kcp/tmc/pkg/virtual/syncer/transformations" + "github.com/kcp-dev/kcp/tmc/pkg/virtual/syncer/upsyncer" ) const ( diff --git a/pkg/virtual/syncer/builder/forwarding.go b/tmc/pkg/virtual/syncer/builder/forwarding.go similarity index 100% rename from pkg/virtual/syncer/builder/forwarding.go rename to tmc/pkg/virtual/syncer/builder/forwarding.go diff --git a/pkg/virtual/syncer/builder/template.go b/tmc/pkg/virtual/syncer/builder/template.go similarity index 98% rename from pkg/virtual/syncer/builder/template.go rename to tmc/pkg/virtual/syncer/builder/template.go index e293ad1f443..399a253d147 100644 --- a/pkg/virtual/syncer/builder/template.go +++ b/tmc/pkg/virtual/syncer/builder/template.go @@ -46,8 +46,8 @@ import ( dynamiccontext "github.com/kcp-dev/kcp/pkg/virtual/framework/dynamic/context" "github.com/kcp-dev/kcp/pkg/virtual/framework/forwardingregistry" "github.com/kcp-dev/kcp/pkg/virtual/framework/transforming" - syncercontext "github.com/kcp-dev/kcp/pkg/virtual/syncer/context" - "github.com/kcp-dev/kcp/pkg/virtual/syncer/controllers/apireconciler" + syncercontext "github.com/kcp-dev/kcp/tmc/pkg/virtual/syncer/context" + "github.com/kcp-dev/kcp/tmc/pkg/virtual/syncer/controllers/apireconciler" ) type templateProvider struct { diff --git a/pkg/virtual/syncer/context/keys.go b/tmc/pkg/virtual/syncer/context/keys.go similarity index 100% rename from pkg/virtual/syncer/context/keys.go rename to tmc/pkg/virtual/syncer/context/keys.go diff --git a/pkg/virtual/syncer/controllers/apireconciler/syncer_apireconciler_controller.go b/tmc/pkg/virtual/syncer/controllers/apireconciler/syncer_apireconciler_controller.go similarity index 100% rename from pkg/virtual/syncer/controllers/apireconciler/syncer_apireconciler_controller.go rename to tmc/pkg/virtual/syncer/controllers/apireconciler/syncer_apireconciler_controller.go diff --git a/pkg/virtual/syncer/controllers/apireconciler/syncer_apireconciler_indexes.go b/tmc/pkg/virtual/syncer/controllers/apireconciler/syncer_apireconciler_indexes.go similarity index 100% rename from pkg/virtual/syncer/controllers/apireconciler/syncer_apireconciler_indexes.go rename to tmc/pkg/virtual/syncer/controllers/apireconciler/syncer_apireconciler_indexes.go diff --git a/pkg/virtual/syncer/controllers/apireconciler/syncer_apireconciler_reconcile.go b/tmc/pkg/virtual/syncer/controllers/apireconciler/syncer_apireconciler_reconcile.go similarity index 99% rename from pkg/virtual/syncer/controllers/apireconciler/syncer_apireconciler_reconcile.go rename to tmc/pkg/virtual/syncer/controllers/apireconciler/syncer_apireconciler_reconcile.go index e5034cf8907..4402a976afc 100644 --- a/pkg/virtual/syncer/controllers/apireconciler/syncer_apireconciler_reconcile.go +++ b/tmc/pkg/virtual/syncer/controllers/apireconciler/syncer_apireconciler_reconcile.go @@ -34,7 +34,7 @@ import ( "github.com/kcp-dev/kcp/pkg/logging" "github.com/kcp-dev/kcp/pkg/virtual/framework/dynamic/apidefinition" dynamiccontext "github.com/kcp-dev/kcp/pkg/virtual/framework/dynamic/context" - syncerbuiltin "github.com/kcp-dev/kcp/pkg/virtual/syncer/schemas/builtin" + syncerbuiltin "github.com/kcp-dev/kcp/tmc/pkg/virtual/syncer/schemas/builtin" ) func (c *APIReconciler) reconcile(ctx context.Context, apiDomainKey dynamiccontext.APIDomainKey, syncTarget *workloadv1alpha1.SyncTarget) error { diff --git a/pkg/virtual/syncer/doc.go b/tmc/pkg/virtual/syncer/doc.go similarity index 100% rename from pkg/virtual/syncer/doc.go rename to tmc/pkg/virtual/syncer/doc.go diff --git a/pkg/virtual/syncer/options/options.go b/tmc/pkg/virtual/syncer/options/options.go similarity index 96% rename from pkg/virtual/syncer/options/options.go rename to tmc/pkg/virtual/syncer/options/options.go index 4927c773217..ede4fbccdcd 100644 --- a/pkg/virtual/syncer/options/options.go +++ b/tmc/pkg/virtual/syncer/options/options.go @@ -25,7 +25,7 @@ import ( kcpinformers "github.com/kcp-dev/kcp/pkg/client/informers/externalversions" "github.com/kcp-dev/kcp/pkg/virtual/framework/rootapiserver" - "github.com/kcp-dev/kcp/pkg/virtual/syncer/builder" + "github.com/kcp-dev/kcp/tmc/pkg/virtual/syncer/builder" ) type Syncer struct{} diff --git a/pkg/virtual/syncer/schemas/builtin/builtin.go b/tmc/pkg/virtual/syncer/schemas/builtin/builtin.go similarity index 100% rename from pkg/virtual/syncer/schemas/builtin/builtin.go rename to tmc/pkg/virtual/syncer/schemas/builtin/builtin.go diff --git a/pkg/virtual/syncer/schemas/builtin/builtin_test.go b/tmc/pkg/virtual/syncer/schemas/builtin/builtin_test.go similarity index 100% rename from pkg/virtual/syncer/schemas/builtin/builtin_test.go rename to tmc/pkg/virtual/syncer/schemas/builtin/builtin_test.go diff --git a/pkg/virtual/syncer/transformations/defaultsummarizing.go b/tmc/pkg/virtual/syncer/transformations/defaultsummarizing.go similarity index 100% rename from pkg/virtual/syncer/transformations/defaultsummarizing.go rename to tmc/pkg/virtual/syncer/transformations/defaultsummarizing.go diff --git a/pkg/virtual/syncer/transformations/helpers.go b/tmc/pkg/virtual/syncer/transformations/helpers.go similarity index 100% rename from pkg/virtual/syncer/transformations/helpers.go rename to tmc/pkg/virtual/syncer/transformations/helpers.go diff --git a/pkg/virtual/syncer/transformations/specdiff.go b/tmc/pkg/virtual/syncer/transformations/specdiff.go similarity index 100% rename from pkg/virtual/syncer/transformations/specdiff.go rename to tmc/pkg/virtual/syncer/transformations/specdiff.go diff --git a/pkg/virtual/syncer/transformations/transformer.go b/tmc/pkg/virtual/syncer/transformations/transformer.go similarity index 99% rename from pkg/virtual/syncer/transformations/transformer.go rename to tmc/pkg/virtual/syncer/transformations/transformer.go index 48b9d4aaca1..0b837809b8d 100644 --- a/pkg/virtual/syncer/transformations/transformer.go +++ b/tmc/pkg/virtual/syncer/transformations/transformer.go @@ -40,8 +40,8 @@ import ( "github.com/kcp-dev/kcp/pkg/syncer/shared" dynamiccontext "github.com/kcp-dev/kcp/pkg/virtual/framework/dynamic/context" "github.com/kcp-dev/kcp/pkg/virtual/framework/transforming" - syncercontext "github.com/kcp-dev/kcp/pkg/virtual/syncer/context" . "github.com/kcp-dev/kcp/tmc/pkg/logging" + syncercontext "github.com/kcp-dev/kcp/tmc/pkg/virtual/syncer/context" ) const ( diff --git a/pkg/virtual/syncer/transformations/transformer_test.go b/tmc/pkg/virtual/syncer/transformations/transformer_test.go similarity index 99% rename from pkg/virtual/syncer/transformations/transformer_test.go rename to tmc/pkg/virtual/syncer/transformations/transformer_test.go index 671e019e57f..dd30604fec4 100644 --- a/pkg/virtual/syncer/transformations/transformer_test.go +++ b/tmc/pkg/virtual/syncer/transformations/transformer_test.go @@ -44,7 +44,7 @@ import ( "github.com/kcp-dev/kcp/pkg/client" dynamiccontext "github.com/kcp-dev/kcp/pkg/virtual/framework/dynamic/context" "github.com/kcp-dev/kcp/pkg/virtual/framework/transforming" - syncercontext "github.com/kcp-dev/kcp/pkg/virtual/syncer/context" + syncercontext "github.com/kcp-dev/kcp/tmc/pkg/virtual/syncer/context" ) type mockedClusterClient struct { diff --git a/pkg/virtual/syncer/transformations/types.go b/tmc/pkg/virtual/syncer/transformations/types.go similarity index 100% rename from pkg/virtual/syncer/transformations/types.go rename to tmc/pkg/virtual/syncer/transformations/types.go diff --git a/pkg/virtual/syncer/upsyncer/storage_wrapper.go b/tmc/pkg/virtual/syncer/upsyncer/storage_wrapper.go similarity index 100% rename from pkg/virtual/syncer/upsyncer/storage_wrapper.go rename to tmc/pkg/virtual/syncer/upsyncer/storage_wrapper.go diff --git a/pkg/virtual/syncer/upsyncer/transformer.go b/tmc/pkg/virtual/syncer/upsyncer/transformer.go similarity index 98% rename from pkg/virtual/syncer/upsyncer/transformer.go rename to tmc/pkg/virtual/syncer/upsyncer/transformer.go index 0f08b5d2f68..2f9eee3970b 100644 --- a/pkg/virtual/syncer/upsyncer/transformer.go +++ b/tmc/pkg/virtual/syncer/upsyncer/transformer.go @@ -29,7 +29,7 @@ import ( "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/dynamic" - syncercontext "github.com/kcp-dev/kcp/pkg/virtual/syncer/context" + syncercontext "github.com/kcp-dev/kcp/tmc/pkg/virtual/syncer/context" ) // UpsyncDiffAnnotationPrefix is an internal annotation used on downstream resources to specify a transformation