@@ -17,20 +17,28 @@ limitations under the License.
1717package main
1818
1919import (
20+ "context"
2021 "flag"
22+ "fmt"
2123 "os"
2224
2325 // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
2426 // to ensure that exec-entrypoint and run can make use of them.
27+ apisv1alpha1 "github.com/kcp-dev/kcp/pkg/apis/apis/v1alpha1"
2528 _ "k8s.io/client-go/plugin/pkg/client/auth"
29+ "k8s.io/client-go/rest"
2630
2731 "k8s.io/apimachinery/pkg/runtime"
32+ "k8s.io/apimachinery/pkg/types"
2833 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2934 clientgoscheme "k8s.io/client-go/kubernetes/scheme"
3035 ctrl "sigs.k8s.io/controller-runtime"
36+ "sigs.k8s.io/controller-runtime/pkg/client"
3137 "sigs.k8s.io/controller-runtime/pkg/healthz"
38+ "sigs.k8s.io/controller-runtime/pkg/kcp"
3239 "sigs.k8s.io/controller-runtime/pkg/log/zap"
3340
41+ "github.com/kcp-dev/catalog/api/v1alpha1"
3442 catalogv1alpha1 "github.com/kcp-dev/catalog/api/v1alpha1"
3543 "github.com/kcp-dev/catalog/controllers"
3644 //+kubebuilder:scaffold:imports
@@ -52,6 +60,8 @@ func main() {
5260 var metricsAddr string
5361 var enableLeaderElection bool
5462 var probeAddr string
63+ var apiExportName string
64+ flag .StringVar (& apiExportName , "api-export-name" , "" , "The name of the APIExport." )
5565 flag .StringVar (& metricsAddr , "metrics-bind-address" , ":8080" , "The address the metric endpoint binds to." )
5666 flag .StringVar (& probeAddr , "health-probe-bind-address" , ":8081" , "The address the probe endpoint binds to." )
5767 flag .BoolVar (& enableLeaderElection , "leader-elect" , false ,
@@ -64,17 +74,38 @@ func main() {
6474 flag .Parse ()
6575
6676 ctrl .SetLogger (zap .New (zap .UseFlagOptions (& opts )))
77+ ctx := ctrl .SetupSignalHandler ()
6778
68- mgr , err := ctrl .NewManager (ctrl .GetConfigOrDie (), ctrl.Options {
79+ restConfig := ctrl .GetConfigOrDie ()
80+ setupLog = setupLog .WithValues ("api-export-name" , apiExportName )
81+
82+ var mgr ctrl.Manager
83+ var err error
84+ options := ctrl.Options {
6985 Scheme : scheme ,
7086 MetricsBindAddress : metricsAddr ,
7187 Port : 9443 ,
7288 HealthProbeBindAddress : probeAddr ,
7389 LeaderElection : enableLeaderElection ,
74- LeaderElectionID : "eaf0b9ae.apis.kcp.dev" ,
75- })
90+ LeaderElectionID : "68a0532d.my.domain" ,
91+ LeaderElectionConfig : restConfig ,
92+ }
93+
94+ // This controller is intended to run only on kcp, hence use
95+ // only cluster aware config.
96+ setupLog .Info ("Looking up virtual workspace URL" )
97+ // TODO: verify if rest config should look at APIExport endpoint or not
98+ cfg , err := restConfigForAPIExport (ctx , restConfig , apiExportName )
99+ if err != nil {
100+ setupLog .Error (err , "error looking up virtual workspace URL" )
101+ }
102+
103+ setupLog .Info ("Using virtual workspace URL" , "url" , cfg .Host )
104+
105+ options .LeaderElectionConfig = restConfig
106+ mgr , err = kcp .NewClusterAwareManager (cfg , options )
76107 if err != nil {
77- setupLog .Error (err , "unable to start manager" )
108+ setupLog .Error (err , "unable to start cluster aware manager" )
78109 os .Exit (1 )
79110 }
80111
@@ -102,3 +133,48 @@ func main() {
102133 os .Exit (1 )
103134 }
104135}
136+
137+ // restConfigForAPIExport returns a *rest.Config properly configured to communicate with the endpoint for the
138+ // APIExport's virtual workspace.
139+ func restConfigForAPIExport (ctx context.Context , cfg * rest.Config , apiExportName string ) (* rest.Config , error ) {
140+ scheme := runtime .NewScheme ()
141+ if err := v1alpha1 .AddToScheme (scheme ); err != nil {
142+ return nil , fmt .Errorf ("error adding apis.kcp.dev/v1alpha1 to scheme: %w" , err )
143+ }
144+
145+ apiExportClient , err := client .New (cfg , client.Options {Scheme : scheme })
146+ if err != nil {
147+ return nil , fmt .Errorf ("error creating APIExport client: %w" , err )
148+ }
149+
150+ var apiExport apisv1alpha1.APIExport
151+
152+ if apiExportName != "" {
153+ if err := apiExportClient .Get (ctx , types.NamespacedName {Name : apiExportName }, & apiExport ); err != nil {
154+ return nil , fmt .Errorf ("error getting APIExport %q: %w" , apiExportName , err )
155+ }
156+ } else {
157+ setupLog .Info ("api-export-name is empty - listing" )
158+ exports := & apisv1alpha1.APIExportList {}
159+ if err := apiExportClient .List (ctx , exports ); err != nil {
160+ return nil , fmt .Errorf ("error listing APIExports: %w" , err )
161+ }
162+ if len (exports .Items ) == 0 {
163+ return nil , fmt .Errorf ("no APIExport found" )
164+ }
165+ if len (exports .Items ) > 1 {
166+ return nil , fmt .Errorf ("more than one APIExport found" )
167+ }
168+ apiExport = exports .Items [0 ]
169+ }
170+
171+ if len (apiExport .Status .VirtualWorkspaces ) < 1 {
172+ return nil , fmt .Errorf ("APIExport %q status.virtualWorkspaces is empty" , apiExportName )
173+ }
174+
175+ cfg = rest .CopyConfig (cfg )
176+ // TODO(ncdc): sharding support
177+ cfg .Host = apiExport .Status .VirtualWorkspaces [0 ].URL
178+
179+ return cfg , nil
180+ }
0 commit comments