Skip to content

Commit 713287e

Browse files
authored
Extend none isolation to APIServiceNamespaces (#432)
* Rename clusterscopeisolation to isolation Signed-off-by: Mangirdas Judeikis <mangirdas@judeikis.lt> On-behalf-of: @SAP mangirdas.judeikis@sap.com
1 parent f4f4064 commit 713287e

24 files changed

Lines changed: 562 additions & 178 deletions

File tree

backend/controllers/serviceexportrequest/serviceexportrequest_controller.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ type APIServiceExportRequestReconciler struct {
4949
manager mcmanager.Manager
5050
opts controller.TypedOptions[mcreconcile.Request]
5151

52-
informerScope kubebindv1alpha2.InformerScope
53-
clusterScopedIsolation kubebindv1alpha2.Isolation
54-
reconciler reconciler
52+
informerScope kubebindv1alpha2.InformerScope
53+
isolation kubebindv1alpha2.Isolation
54+
reconciler reconciler
5555
}
5656

5757
// NewAPIServiceExportRequestReconciler returns a new APIServiceExportRequestReconciler to reconcile APIServiceExportRequests.
@@ -75,14 +75,14 @@ func NewAPIServiceExportRequestReconciler(
7575
}
7676

7777
r := &APIServiceExportRequestReconciler{
78-
manager: mgr,
79-
opts: opts,
80-
informerScope: scope,
81-
clusterScopedIsolation: isolation,
78+
manager: mgr,
79+
opts: opts,
80+
informerScope: scope,
81+
isolation: isolation,
8282
reconciler: reconciler{
83-
informerScope: scope,
84-
clusterScopedIsolation: isolation,
85-
schemaSource: schemaSource,
83+
informerScope: scope,
84+
isolation: isolation,
85+
schemaSource: schemaSource,
8686
getBoundSchema: func(ctx context.Context, cl client.Client, namespace, name string) (*kubebindv1alpha2.BoundSchema, error) {
8787
var schema kubebindv1alpha2.BoundSchema
8888
key := types.NamespacedName{Namespace: namespace, Name: name}

backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ import (
4040
)
4141

4242
type reconciler struct {
43-
informerScope kubebindv1alpha2.InformerScope
44-
clusterScopedIsolation kubebindv1alpha2.Isolation
45-
schemaSource string
43+
informerScope kubebindv1alpha2.InformerScope
44+
isolation kubebindv1alpha2.Isolation
45+
schemaSource string
4646

4747
getBoundSchema func(ctx context.Context, cl client.Client, namespace, name string) (*kubebindv1alpha2.BoundSchema, error)
4848
createBoundSchema func(ctx context.Context, cl client.Client, schema *kubebindv1alpha2.BoundSchema) error
@@ -134,7 +134,7 @@ func (r *reconciler) getExportedSchemas(ctx context.Context, cl client.Client) (
134134
return boundSchemas, nil
135135
}
136136

137-
func (r *reconciler) ensureBoundSchemas(ctx context.Context, cl client.Client, cache cache.Cache, req *kubebindv1alpha2.APIServiceExportRequest) error {
137+
func (r *reconciler) ensureBoundSchemas(ctx context.Context, cl client.Client, _ cache.Cache, req *kubebindv1alpha2.APIServiceExportRequest) error {
138138
exportedSchemas, err := r.getExportedSchemas(ctx, cl)
139139
if err != nil {
140140
return err
@@ -167,7 +167,7 @@ func (r *reconciler) ensureBoundSchemas(ctx context.Context, cl client.Client, c
167167
// we need to rewrite the BoundSchema's scope accordingly. For all
168168
// other isolation strategies, as well as for namespaced schemas,
169169
// no changes are necessary.
170-
if boundSchema.Spec.Scope == apiextensionsv1.NamespaceScoped && r.clusterScopedIsolation == kubebindv1alpha2.IsolationNamespaced {
170+
if boundSchema.Spec.Scope == apiextensionsv1.NamespaceScoped && r.isolation == kubebindv1alpha2.IsolationNamespaced {
171171
boundSchema.Spec.Scope = apiextensionsv1.ClusterScoped
172172
}
173173

@@ -185,7 +185,6 @@ func (r *reconciler) ensureExports(ctx context.Context, cl client.Client, cache
185185
logger := klog.FromContext(ctx)
186186

187187
var schemas []*kubebindv1alpha2.BoundSchema
188-
var scope apiextensionsv1.ResourceScope
189188
if req.Status.Phase == kubebindv1alpha2.APIServiceExportRequestPhasePending {
190189
for _, res := range req.Spec.Resources {
191190
name := res.ResourceGroupName()
@@ -207,7 +206,6 @@ func (r *reconciler) ensureExports(ctx context.Context, cl client.Client, cache
207206

208207
// Collect all schemas for hashing.
209208
// TODO(mjudeikis) Scope is same for all crds so we keep stamping it over. We might want to change this
210-
scope = boundSchema.Spec.Scope
211209
schemas = append(schemas, boundSchema)
212210
}
213211

@@ -236,11 +234,9 @@ func (r *reconciler) ensureExports(ctx context.Context, cl client.Client, cache
236234
},
237235
Spec: kubebindv1alpha2.APIServiceExportSpec{
238236
InformerScope: r.informerScope,
237+
Isolation: r.isolation,
239238
},
240239
}
241-
if scope == apiextensionsv1.ClusterScoped {
242-
export.Spec.ClusterScopedIsolation = r.clusterScopedIsolation
243-
}
244240

245241
for _, res := range req.Spec.Resources {
246242
export.Spec.Resources = append(export.Spec.Resources, kubebindv1alpha2.APIServiceExportResource{

backend/controllers/servicenamespace/servicenamespace_controller.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ type APIServiceNamespaceReconciler struct {
5050
manager mcmanager.Manager
5151
opts controller.TypedOptions[mcreconcile.Request]
5252

53-
informerScope kubebindv1alpha2.InformerScope
54-
clusterScopedIsolation kubebindv1alpha2.Isolation
55-
reconciler reconciler
53+
informerScope kubebindv1alpha2.InformerScope
54+
isolation kubebindv1alpha2.Isolation
55+
reconciler reconciler
5656
}
5757

5858
// NewAPIServiceNamespaceReconciler returns a new APIServiceNamespaceReconciler to reconcile APIServiceNamespaces.
@@ -70,12 +70,13 @@ func NewAPIServiceNamespaceReconciler(
7070
}
7171

7272
r := &APIServiceNamespaceReconciler{
73-
manager: mgr,
74-
opts: opts,
75-
informerScope: scope,
76-
clusterScopedIsolation: isolation,
73+
manager: mgr,
74+
opts: opts,
75+
informerScope: scope,
76+
isolation: isolation,
7777
reconciler: reconciler{
78-
scope: scope,
78+
scope: scope,
79+
isolation: isolation,
7980

8081
getNamespace: func(ctx context.Context, cache cache.Cache, name string) (*corev1.Namespace, error) {
8182
var ns corev1.Namespace

backend/controllers/servicenamespace/servicenamespace_reconcile.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ import (
3939
)
4040

4141
type reconciler struct {
42-
scope kubebindv1alpha2.InformerScope
42+
scope kubebindv1alpha2.InformerScope
43+
isolation kubebindv1alpha2.Isolation
4344

4445
getNamespace func(ctx context.Context, cache cache.Cache, name string) (*corev1.Namespace, error)
4546
createNamespace func(ctx context.Context, client client.Client, ns *corev1.Namespace) error
@@ -52,11 +53,20 @@ type reconciler struct {
5253

5354
func (c *reconciler) reconcile(ctx context.Context, client client.Client, cache cache.Cache, sns *kubebindv1alpha2.APIServiceNamespace) error {
5455
var ns *corev1.Namespace
55-
nsName := sns.Namespace + "-" + sns.Name
56-
if sns.Status.Namespace != "" {
56+
var nsName string
57+
switch {
58+
case sns.Status.Namespace != "":
59+
// use existing namespace from status
5760
nsName = sns.Status.Namespace
58-
ns, _ = c.getNamespace(ctx, cache, nsName) // golint:errcheck
61+
case c.isolation == kubebindv1alpha2.IsolationNone:
62+
nsName = sns.Name
63+
case c.isolation == kubebindv1alpha2.IsolationNamespaced || c.isolation == kubebindv1alpha2.IsolationPrefixed:
64+
nsName = sns.Namespace + "-" + sns.Name
65+
default:
66+
return fmt.Errorf("unknown isolation strategy: %s", c.isolation)
5967
}
68+
ns, _ = c.getNamespace(ctx, cache, nsName)
69+
6070
if ns == nil {
6171
ns = &corev1.Namespace{
6272
ObjectMeta: metav1.ObjectMeta{

backend/oidc/oidc_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func TestLoadTLSConfig_Success(t *testing.T) {
5353
// Verify the TLS config was created
5454
if config == nil {
5555
t.Fatal("Expected non-nil TLS config")
56+
return
5657
}
5758

5859
if config.RootCAs == nil {
@@ -194,6 +195,7 @@ func TestLoadTLSConfig_MultipleCerts(t *testing.T) {
194195
// Verify the TLS config was created
195196
if config == nil {
196197
t.Fatal("Expected non-nil TLS config")
198+
return
197199
}
198200

199201
if config.RootCAs == nil {

backend/options/options.go

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ type ExtraOptions struct {
4848

4949
Provider string
5050

51-
NamespacePrefix string
52-
PrettyName string
53-
ConsumerScope string
54-
ClusterScopedIsolation string
55-
ExternalAddress string
56-
ExternalCAFile string
57-
ExternalCA []byte
58-
TLSExternalServerName string
51+
NamespacePrefix string
52+
PrettyName string
53+
ConsumerScope string
54+
Isolation string
55+
ExternalAddress string
56+
ExternalCAFile string
57+
ExternalCA []byte
58+
TLSExternalServerName string
5959
// Defines the source of the schema for the bind screen.
6060
// Options are:
6161
// CustomResourceDefinition.v1.apiextensions.k8s.io
@@ -101,14 +101,14 @@ func NewOptions() *Options {
101101
ProviderKcp: providerkcp.NewOptions(),
102102

103103
ExtraOptions: ExtraOptions{
104-
Provider: "kubernetes",
105-
NamespacePrefix: "cluster-",
106-
PrettyName: "Backend",
107-
ConsumerScope: string(kubebindv1alpha2.NamespacedScope),
108-
ClusterScopedIsolation: string(kubebindv1alpha2.IsolationPrefixed),
109-
SchemaSource: CustomResourceDefinitionSource.String(),
110-
Frontend: "embedded", // Not used, but indicates to use embedded frontend using SPA.
111-
TokenExpiry: 1 * time.Hour,
104+
Provider: "kubernetes",
105+
NamespacePrefix: "cluster-",
106+
PrettyName: "Backend",
107+
ConsumerScope: string(kubebindv1alpha2.NamespacedScope),
108+
Isolation: string(kubebindv1alpha2.IsolationPrefixed),
109+
SchemaSource: CustomResourceDefinitionSource.String(),
110+
Frontend: "embedded", // Not used, but indicates to use embedded frontend using SPA.
111+
TokenExpiry: 1 * time.Hour,
112112
},
113113
}
114114
return opts
@@ -151,7 +151,10 @@ func (options *Options) AddFlags(fs *pflag.FlagSet) {
151151
fs.StringVar(&options.NamespacePrefix, "namespace-prefix", options.NamespacePrefix, "The prefix to use for cluster namespaces")
152152
fs.StringVar(&options.PrettyName, "pretty-name", options.PrettyName, "Pretty name for the backend")
153153
fs.StringVar(&options.ConsumerScope, "consumer-scope", options.ConsumerScope, "How consumers access the service provider cluster. In Kubernetes, \"namespaced\" allows namespace isolation. In kcp, \"cluster\" allows workspace isolation, and with that allows cluster-scoped resources to bind and it is generally more performant.")
154-
fs.StringVar(&options.ClusterScopedIsolation, "cluster-scoped-isolation", options.ClusterScopedIsolation, "How cluster scoped service objects are isolated between multiple consumers on the provider side. Among the choices, \"prefixed\" prepends the name of the cluster namespace to an object's name; \"namespaced\" maps a consumer side object into a namespaced object inside the corresponding cluster namespace; \"none\" is used for the case of a dedicated provider where isolation is not necessary.")
154+
// TODO(mjudeikis): remove deprecated flag in future release
155+
fs.StringVar(&options.Isolation, "cluster-scoped-isolation", options.Isolation, "How cluster scoped service objects are isolated between multiple consumers on the provider side. Among the choices, \"prefixed\" prepends the name of the cluster namespace to an object's name; \"namespaced\" maps a consumer side object into a namespaced object inside the corresponding cluster namespace; \"none\" is used for the case of a dedicated provider where isolation is not necessary.")
156+
_ = fs.MarkDeprecated("cluster-scoped-isolation", "use --isolation instead")
157+
fs.StringVar(&options.Isolation, "isolation", options.Isolation, "How cluster scoped service objects are isolated between multiple consumers on the provider side. Among the choices, \"prefixed\" prepends the name of the cluster namespace to an object's name; \"namespaced\" maps a consumer side object into a namespaced object inside the corresponding cluster namespace; \"none\" is used for the case of a dedicated provider where isolation is not necessary.")
155158
fs.StringVar(&options.ExternalAddress, "external-address", options.ExternalAddress, "The external address for the service provider cluster, including https:// and port. If not specified, service account's hosts are used.")
156159
fs.StringVar(&options.ExternalCAFile, "external-ca-file", options.ExternalCAFile, "The external CA file for the service provider cluster. If not specified, service account's CA is used.")
157160
fs.StringVar(&options.TLSExternalServerName, "external-server-name", options.TLSExternalServerName, "The external (TLS) server name used by consumers to talk to the service provider cluster. This can be useful to select the right certificate via SNI.")
@@ -200,13 +203,15 @@ func (options *Options) Complete() (*CompletedOptions, error) {
200203
if strings.ToLower(options.ConsumerScope) == "cluster" {
201204
options.ConsumerScope = string(kubebindv1alpha2.ClusterScope)
202205
}
203-
switch strings.ToLower(options.ClusterScopedIsolation) {
206+
switch strings.ToLower(options.Isolation) {
204207
case "prefixed":
205-
options.ClusterScopedIsolation = string(kubebindv1alpha2.IsolationPrefixed)
208+
options.Isolation = string(kubebindv1alpha2.IsolationPrefixed)
206209
case "namespaced":
207-
options.ClusterScopedIsolation = string(kubebindv1alpha2.IsolationNamespaced)
210+
options.Isolation = string(kubebindv1alpha2.IsolationNamespaced)
208211
case "none":
209-
options.ClusterScopedIsolation = string(kubebindv1alpha2.IsolationNone)
212+
options.Isolation = string(kubebindv1alpha2.IsolationNone)
213+
default:
214+
options.Isolation = string(kubebindv1alpha2.IsolationNone)
210215
}
211216

212217
if options.ExternalCAFile != "" && options.ExternalCA != nil {

backend/server.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ func NewServer(ctx context.Context, c *Config) (*Server, error) {
179179
s.Config.Manager,
180180
opts,
181181
kubebindv1alpha2.InformerScope(c.Options.ConsumerScope),
182-
kubebindv1alpha2.Isolation(c.Options.ClusterScopedIsolation),
182+
kubebindv1alpha2.Isolation(c.Options.Isolation),
183183
)
184184
if err != nil {
185185
return nil, fmt.Errorf("error setting up APIServiceNamespace Controller: %w", err)
@@ -195,7 +195,7 @@ func NewServer(ctx context.Context, c *Config) (*Server, error) {
195195
s.Config.Manager,
196196
opts,
197197
kubebindv1alpha2.InformerScope(c.Options.ConsumerScope),
198-
kubebindv1alpha2.Isolation(c.Options.ClusterScopedIsolation),
198+
kubebindv1alpha2.Isolation(c.Options.Isolation),
199199
c.Options.SchemaSource,
200200
)
201201
if err != nil {

cli/pkg/kubectl/dev/plugin/create.go

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ package plugin
1919
import (
2020
"bufio"
2121
"context"
22+
"encoding/json"
2223
"fmt"
24+
"io"
25+
"net/http"
2326
"os"
2427
"os/exec"
2528
"runtime"
@@ -67,6 +70,14 @@ type DevOptions struct {
6770
KindNetwork string
6871
}
6972

73+
// fallbackAssetVersion is used when unable to fetch the latest version
74+
const fallbackAssetVersion = "0.6.0"
75+
76+
// gitHubRelease represents a GitHub release response
77+
type gitHubRelease struct {
78+
TagName string `json:"tag_name"`
79+
}
80+
7081
// NewDevOptions creates a new DevOptions
7182
func NewDevOptions(streams genericclioptions.IOStreams) *DevOptions {
7283
opts := base.NewOptions(streams)
@@ -77,7 +88,7 @@ func NewDevOptions(streams genericclioptions.IOStreams) *DevOptions {
7788
ProviderClusterName: "kind-provider",
7889
ConsumerClusterName: "kind-consumer",
7990
ChartPath: "oci://ghcr.io/kube-bind/charts/backend",
80-
ChartVersion: "v0.6.0",
91+
ChartVersion: fallbackAssetVersion,
8192
}
8293
}
8394

@@ -92,15 +103,74 @@ func (o *DevOptions) AddCmdFlags(cmd *cobra.Command) {
92103
cmd.Flags().StringVar(&o.ChartPath, "chart-path", o.ChartPath, "Helm chart path or OCI registry URL")
93104
cmd.Flags().StringVar(&o.ChartVersion, "chart-version", o.ChartVersion, "Helm chart version")
94105
cmd.Flags().StringVar(&o.Image, "image", "ghcr.io/kube-bind/backend", "kube-bind backend image to use in dev mode")
95-
cmd.Flags().StringVar(&o.Tag, "tag", "main", "kube-bind backend image tag to use in dev mode")
106+
cmd.Flags().StringVar(&o.Tag, "tag", "", "kube-bind backend image tag to use in dev mode")
96107
cmd.Flags().StringVar(&o.KindNetwork, "kind-network", "kube-bind-dev", "kind network to use in dev mode")
97108
}
98109

99110
// Complete completes the options
100111
func (o *DevOptions) Complete(args []string) error {
112+
// Only fetch the latest version if tag is not set
113+
var assetVersion string
114+
if o.Tag == "" {
115+
version, err := fetchLatestRelease()
116+
if err != nil {
117+
// Log the error but continue with fallback version
118+
fmt.Fprintf(o.Streams.ErrOut, "Warning: Failed to fetch latest release version: %v. Using fallback version %s\n", err, fallbackAssetVersion)
119+
assetVersion = fallbackAssetVersion
120+
} else {
121+
assetVersion = version
122+
}
123+
124+
// Update options with the resolved version
125+
if o.ChartVersion == "" || o.ChartVersion == fallbackAssetVersion {
126+
o.ChartVersion = assetVersion
127+
}
128+
if o.Tag == "" || o.Tag == "v"+fallbackAssetVersion {
129+
o.Tag = "v" + assetVersion
130+
}
131+
}
132+
101133
return nil
102134
}
103135

136+
// fetchLatestRelease fetches the latest release version from GitHub
137+
func fetchLatestRelease() (string, error) {
138+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
139+
defer cancel()
140+
141+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.github.com/repos/kube-bind/kube-bind/releases/latest", nil)
142+
if err != nil {
143+
return "", fmt.Errorf("failed to create request: %w", err)
144+
}
145+
146+
resp, err := http.DefaultClient.Do(req)
147+
if err != nil {
148+
return "", fmt.Errorf("failed to fetch latest release: %w", err)
149+
}
150+
defer resp.Body.Close()
151+
152+
if resp.StatusCode != http.StatusOK {
153+
return "", fmt.Errorf("GitHub API returned status %d", resp.StatusCode)
154+
}
155+
156+
body, err := io.ReadAll(resp.Body)
157+
if err != nil {
158+
return "", fmt.Errorf("failed to read response body: %w", err)
159+
}
160+
161+
var release gitHubRelease
162+
if err := json.Unmarshal(body, &release); err != nil {
163+
return "", fmt.Errorf("failed to parse release data: %w", err)
164+
}
165+
166+
if release.TagName == "" {
167+
return "", fmt.Errorf("no tag name in release data")
168+
}
169+
170+
version := strings.TrimPrefix(release.TagName, "v")
171+
return version, nil
172+
}
173+
104174
// Validate validates the options
105175
func (o *DevOptions) Validate() error {
106176
return o.Options.Validate()

contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ spec:
6262
crd: {}
6363
- group: kube-bind.io
6464
name: apiserviceexports
65-
schema: v251112-503d98b.apiserviceexports.kube-bind.io
65+
schema: v260119-2e5a3e93.apiserviceexports.kube-bind.io
6666
storage:
6767
crd: {}
6868
- group: kube-bind.io

0 commit comments

Comments
 (0)