@@ -21,8 +21,10 @@ import (
2121 "errors"
2222 "fmt"
2323 "net/http"
24+ "regexp"
2425 "strings"
2526
27+ "github.com/kcp-dev/logicalcluster/v3"
2628 "go.uber.org/zap"
2729
2830 "k8s.io/apimachinery/pkg/api/meta"
@@ -32,6 +34,7 @@ import (
3234 "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
3335 "sigs.k8s.io/controller-runtime/pkg/cluster"
3436 "sigs.k8s.io/controller-runtime/pkg/kcp"
37+ "sigs.k8s.io/controller-runtime/pkg/kontext"
3538)
3639
3740// Cluster is a controller-runtime cluster
@@ -57,11 +60,76 @@ func newWildcardClusterMapperProvider(c *rest.Config, httpClient *http.Client) (
5760 return apiutil .NewDynamicRESTMapper (mapperCfg , httpClient )
5861}
5962
63+ // clusterAwareRoundTripper is a cluster-aware wrapper around http.RoundTripper
64+ // taking the cluster from the context.
65+ type clusterAwareRoundTripper struct {
66+ delegate http.RoundTripper
67+ }
68+
69+ // newClusterAwareRoundTripper creates a new cluster aware round tripper.
70+ func newClusterAwareRoundTripper (delegate http.RoundTripper ) * clusterAwareRoundTripper {
71+ return & clusterAwareRoundTripper {
72+ delegate : delegate ,
73+ }
74+ }
75+
76+ func (c * clusterAwareRoundTripper ) RoundTrip (req * http.Request ) (* http.Response , error ) {
77+ cluster , ok := kontext .ClusterFrom (req .Context ())
78+ if ok && ! cluster .Empty () {
79+ return clusterRoundTripper {cluster : cluster .Path (), delegate : c .delegate }.RoundTrip (req )
80+ }
81+ return c .delegate .RoundTrip (req )
82+ }
83+
84+ // clusterRoundTripper is static cluster-aware wrapper around http.RoundTripper.
85+ type clusterRoundTripper struct {
86+ cluster logicalcluster.Path
87+ delegate http.RoundTripper
88+ }
89+
90+ func (c clusterRoundTripper ) RoundTrip (req * http.Request ) (* http.Response , error ) {
91+ if ! c .cluster .Empty () {
92+ req = req .Clone (req .Context ())
93+ req .URL .Path = generatePath (req .URL .Path , c .cluster )
94+ req .URL .RawPath = generatePath (req .URL .RawPath , c .cluster )
95+ }
96+ return c .delegate .RoundTrip (req )
97+ }
98+
99+ // apiRegex matches any string that has /api/ or /apis/ in it.
100+ var apiRegex = regexp .MustCompile (`(/api/|/apis/)` )
101+
102+ // generatePath formats the request path to target the specified cluster.
103+ func generatePath (originalPath string , clusterPath logicalcluster.Path ) string {
104+ // If the originalPath already has cluster.Path() then the path was already modifed and no change needed
105+ if strings .Contains (originalPath , clusterPath .RequestPath ()) {
106+ return originalPath
107+ }
108+ // If the originalPath has /api/ or /apis/ in it, it might be anywhere in the path, so we use a regex to find and
109+ // replaces /api/ or /apis/ with $cluster/api/ or $cluster/apis/
110+ if apiRegex .MatchString (originalPath ) {
111+ return apiRegex .ReplaceAllString (originalPath , fmt .Sprintf ("%s$1" , clusterPath .RequestPath ()))
112+ }
113+ // Otherwise, we're just prepending /clusters/$name
114+ path := clusterPath .RequestPath ()
115+ // if the original path is relative, add a / separator
116+ if len (originalPath ) > 0 && originalPath [0 ] != '/' {
117+ path += "/"
118+ }
119+ // finally append the original path
120+ path += originalPath
121+ return path
122+ }
123+
60124func NewCluster (address string , baseRestConfig * rest.Config ) (* Cluster , error ) {
61125 // note that this cluster and all its components are kcp-aware
62126 config := rest .CopyConfig (baseRestConfig )
63127 config .Host = address
64128
129+ config .Wrap (func (rt http.RoundTripper ) http.RoundTripper {
130+ return newClusterAwareRoundTripper (rt )
131+ })
132+
65133 clusterObj , err := cluster .New (config , func (o * cluster.Options ) {
66134 o .NewCache = kcp .NewClusterAwareCache
67135 o .NewAPIReader = kcp .NewClusterAwareAPIReader
0 commit comments