Skip to content

Commit dfc18e0

Browse files
authored
Merge pull request #1 from mjudeikis/v1alpha2-apiserviceexport-patch
V1alpha2 apiserviceexport patch
2 parents 62bd128 + 4bc8f75 commit dfc18e0

18 files changed

Lines changed: 251 additions & 92 deletions

File tree

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ $(KCP):
255255
tar xz -C "$(TOOLS_DIR)" --strip-components="1" bin/kcp
256256
mv $(TOOLS_DIR)/kcp $(KCP)
257257

258+
run-kcp: $(KCP)
259+
$(KCP) start
260+
258261
.PHONY: test-e2e
259262
ifdef USE_GOTESTSUM
260263
test-e2e: $(GOTESTSUM)

README.md

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ All the actions shown between the clusters are done by the konnector, except: th
6060

6161
## Usage
6262

63+
This section allows you to run local kube-bind backend and konnector.
64+
The main challenge when running it locally is to have multiple clusters available and accessible.
65+
66+
For this we use [kcp](https://github.com/kcp-dev/kcp) to create a local clusters under single kcp instance.
67+
By having a single kcp instance, we can have multiple clusters available and accessible via same url.
68+
69+
To run kcp, you need to have a kcp binary.
70+
71+
```shell
72+
$ make run-kcp
73+
```
74+
6375
To run the current backend, there must be an OIDC issuer installed in place to do the
6476
the oauth2 workflow.
6577

@@ -83,8 +95,16 @@ accessible.
8395
***Note: make sure before running the backend that you have the dex server up and running as mentioned above
8496
and that you have at least one k8s cluster. Take a look at the backend option in the cmd/main.go file***
8597

98+
Create copy of kcp kubeconfig and create provider cluster:
99+
100+
```shell
101+
$ cp .kcp/admin.kubeconfig .kcp/provider.kubeconfig
102+
$ export KUBECONFIG=.kcp/provider.kubeconfig
103+
$ kubectl ws create provider --enter
104+
```
105+
86106
* apply the CRDs: `kubectl apply -f deploy/crd`
87-
* In order to populate binding list on website, we need a CRD with label `kube-bind.io/exported: true`. Apply example CRD: `kubectl apply -f deploy/examples/crd-mangodb.yaml`
107+
* In order to populate binding list on website, we need a CRD with label `kube-bind.io/exported: true`. Apply example APIResourceSchema for the CRD: `kubectl apply -f deploy/examples/crd-mangodb.yaml`
88108
* start the backend binary with the right flags:
89109
```shell
90110
$ make build
@@ -110,4 +130,30 @@ WQh88mNOY0Z3tLy1/WOud7qIEEBxz+POc4j8BsYenYo=
110130
The `--cookie-signing-key` option is required and supports 32 and 64 byte lengths.
111131
The `--cookie-encryption-key` option is optional and supports byte lengths of 16, 24, 32 for AES-128, AES-192, or AES-256.
112132

113-
* with a KUBECONFIG against another cluster (a consumer cluster) bind a service: `kubectl bind http://127.0.0.1:8080/export`.
133+
### Consumer
134+
Now create consumer cluster:
135+
136+
```shell
137+
$ export KUBECONFIG=.kcp/admin.kubeconfig
138+
$ kubectl ws create consumer --enter
139+
```
140+
141+
Now create the APIServiceExportRequest:
142+
143+
```shell
144+
$ ./bin/kubectl-bind http://127.0.0.1:8080/export --dry-run -o yaml > apiserviceexport.yaml
145+
# This will wait for konnector to be ready. Once this gets running - start the konnector bellow
146+
$ ./bin/kubectl-bind apiservice --remote-namespace kube-bind-77wsg --remote-kubeconfig .kcp/provider.kubeconfig -f apiserviceexport.yaml --skip-konnector
147+
# run konnector
148+
$ go run ./cmd/konnector/ --lease-namespace default
149+
```
150+
151+
### Limitations
152+
153+
These limitations are part of the roadmap and will be addressed in the future.
154+
155+
* Currently we don't support related resources, like ConfigMaps, Secrets
156+
* Currently CRD resources MUST be installed in the provider cluster, even when APIResourceSchema is used.
157+
This is to allow the konnector to sync instances of the CRD to the consumer cluster.
158+
This should be removed once we introduce sync policies and object wrappers.
159+
* Currently we dont support granular permissions, like only allow to read/write certain named resources.

cli/pkg/kubectl/bind/plugin/authenticate.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ func getProvider(url string, insecure bool) (*kubebindv1alpha2.BindingProvider,
7272
}
7373
if bindSemVer, err := semver.Parse(strings.TrimLeft(bindVersion, "v")); err != nil {
7474
return nil, fmt.Errorf("failed to parse bind version %q: %v", bindVersion, err)
75-
} else if min := semver.MustParse("0.3.0"); bindSemVer.GE(min) {
76-
// we added this in v0.3.0. Don't test before.
75+
} else if min := semver.MustParse("0.5.0"); bindSemVer.GE(min) {
76+
// At v0.5.0 we made breaking change for how APIExports looks like.
77+
// So we need to test for v0.5.0+. If
7778
if err := validateProviderVersion(provider.Version); err != nil {
7879
return nil, err
7980
}
@@ -84,7 +85,7 @@ func getProvider(url string, insecure bool) (*kubebindv1alpha2.BindingProvider,
8485

8586
func validateProviderVersion(providerVersion string) error {
8687
if providerVersion == "" {
87-
return fmt.Errorf("provider version %q is empty, please update the backend to v0.3.0+", providerVersion)
88+
return fmt.Errorf("provider version %q is empty, please update the backend to v0.5.0+", providerVersion)
8889
} else if providerVersion == "v0.0.0" || providerVersion == "v0.0.0-master+$Format:%H$" {
8990
// unversioned, development version
9091
return nil
@@ -94,7 +95,8 @@ func validateProviderVersion(providerVersion string) error {
9495
if err != nil {
9596
return fmt.Errorf("provider version %q cannot be parsed", providerVersion)
9697
}
97-
if min := semver.MustParse("0.3.0"); providerSemVer.LT(min) {
98+
// Check if provider is higher than 0.4.8, we need to have same version of kube-bind to use this provider.
99+
if min := semver.MustParse("0.5.0"); providerSemVer.LT(min) {
98100
return fmt.Errorf("provider version %s is not supported, must be at least v%s", providerVersion, min)
99101
}
100102

contrib/example-backend/controllers/clusterbinding/clusterbinding_reconcile.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,18 @@ func (r *reconciler) ensureRBACClusterRole(ctx context.Context, clusterBinding *
154154
return fmt.Errorf("failed to get APIResourceSchema %w", err)
155155
}
156156

157-
expected.Rules = append(expected.Rules, rbacv1.PolicyRule{
158-
APIGroups: []string{schema.Spec.APIResourceSchemaCRDSpec.Group},
159-
Resources: []string{schema.Spec.APIResourceSchemaCRDSpec.Names.Plural},
160-
Verbs: []string{"get", "list", "watch", "update", "patch", "delete", "create"},
161-
})
157+
expected.Rules = append(expected.Rules,
158+
rbacv1.PolicyRule{
159+
APIGroups: []string{schema.Spec.APIResourceSchemaCRDSpec.Group},
160+
Resources: []string{schema.Spec.APIResourceSchemaCRDSpec.Names.Plural},
161+
Verbs: []string{"get", "list", "watch", "update", "patch", "delete", "create"},
162+
},
163+
rbacv1.PolicyRule{
164+
APIGroups: []string{kubebindv1alpha2.GroupName},
165+
Resources: []string{"apiresourceschemas"},
166+
Verbs: []string{"get", "list", "watch"},
167+
},
168+
)
162169
}
163170
}
164171

contrib/example-backend/controllers/serviceexport/serviceexport_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func NewController(
7979
getCRD: func(name string) (*apiextensionsv1.CustomResourceDefinition, error) {
8080
return crdInformer.Lister().Get(name)
8181
},
82-
getAPIResourceSchema: func(ctx context.Context, ns, name string) (*kubebindv1alpha2.APIResourceSchema, error) {
82+
getAPIResourceSchema: func(ctx context.Context, name string) (*kubebindv1alpha2.APIResourceSchema, error) {
8383
return bindClient.KubeBindV1alpha2().APIResourceSchemas().Get(ctx, name, metav1.GetOptions{})
8484
},
8585
deleteServiceExport: func(ctx context.Context, ns, name string) error {

contrib/example-backend/controllers/serviceexport/serviceexport_reconcile.go

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ package serviceexport
1818

1919
import (
2020
"context"
21+
"crypto/sha256"
22+
"encoding/hex"
23+
"sort"
2124

2225
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2326
"k8s.io/apimachinery/pkg/api/errors"
@@ -26,13 +29,12 @@ import (
2629

2730
kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2"
2831
kubebindhelpers "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2/helpers"
29-
conditionsapi "github.com/kube-bind/kube-bind/sdk/apis/third_party/conditions/apis/conditions/v1alpha1"
3032
"github.com/kube-bind/kube-bind/sdk/apis/third_party/conditions/util/conditions"
3133
)
3234

3335
type reconciler struct {
3436
getCRD func(name string) (*apiextensionsv1.CustomResourceDefinition, error)
35-
getAPIResourceSchema func(ctx context.Context, ns, name string) (*kubebindv1alpha2.APIResourceSchema, error)
37+
getAPIResourceSchema func(ctx context.Context, name string) (*kubebindv1alpha2.APIResourceSchema, error)
3638
deleteServiceExport func(ctx context.Context, namespace, name string) error
3739

3840
requeue func(export *kubebindv1alpha2.APIServiceExport)
@@ -44,7 +46,9 @@ func (r *reconciler) reconcile(ctx context.Context, export *kubebindv1alpha2.API
4446
if specChanged, err := r.ensureSchema(ctx, export); err != nil {
4547
errs = append(errs, err)
4648
} else if specChanged {
47-
r.requeue(export)
49+
// TODO: This should be separate controller for apiresourceschemas.
50+
// This is wrong place now.
51+
// r.requeue(export)
4852
return nil
4953
}
5054

@@ -54,56 +58,50 @@ func (r *reconciler) reconcile(ctx context.Context, export *kubebindv1alpha2.API
5458
func (r *reconciler) ensureSchema(ctx context.Context, export *kubebindv1alpha2.APIServiceExport) (specChanged bool, err error) {
5559
logger := klog.FromContext(ctx)
5660

61+
var leafHashes []string
5762
for _, resourceRef := range export.Spec.Resources {
5863
if resourceRef.Type != "APIResourceSchema" {
5964
logger.V(1).Info("Skipping unsupported resource type", "type", resourceRef.Type)
6065
continue
6166
}
6267

63-
schema, err := r.getAPIResourceSchema(ctx, export.Namespace, resourceRef.Name)
68+
schema, err := r.getAPIResourceSchema(ctx, resourceRef.Name)
6469
if err != nil {
6570
if errors.IsNotFound(err) {
6671
continue
6772
}
6873
return false, err
6974
}
70-
crd, err := r.getCRD(schema.Name)
71-
if err != nil && !errors.IsNotFound(err) {
72-
return false, err
73-
}
7475

75-
if crd == nil {
76-
// CRD missing => delete SER too
77-
logger.V(1).Info("Deleting APIServiceExport because CRD is missing")
78-
return false, r.deleteServiceExport(ctx, export.Namespace, export.Name)
79-
}
76+
hash := kubebindhelpers.APIResourceSchemaCRDSpecHash(&schema.Spec.APIResourceSchemaCRDSpec)
77+
leafHashes = append(leafHashes, hash)
78+
}
8079

81-
expected, err := kubebindhelpers.CRDToAPIResourceSchema(crd, "")
82-
if err != nil {
83-
conditions.MarkFalse(
84-
export,
85-
kubebindv1alpha2.APIServiceExportConditionProviderInSync,
86-
"CustomResourceDefinitionUpdateFailed",
87-
conditionsapi.ConditionSeverityError,
88-
"CustomResourceDefinition %s cannot be converted into a APIServiceExport: %s",
89-
export.Name, err,
90-
)
91-
return false, nil // nothing we can do
92-
}
80+
hashOfHashes := hashOfHashes(leafHashes)
9381

94-
if hash := kubebindhelpers.APIResourceSchemaCRDSpecHash(&expected.Spec.APIResourceSchemaCRDSpec); export.Annotations[kubebindv1alpha2.SourceSpecHashAnnotationKey] != hash {
95-
// both exist, update APIServiceExport
96-
logger.V(1).Info("Updating APIServiceExport")
97-
schema.Spec.APIResourceSchemaCRDSpec = expected.Spec.APIResourceSchemaCRDSpec
98-
if schema.Annotations == nil {
99-
schema.Annotations = map[string]string{}
100-
}
101-
schema.Annotations[kubebindv1alpha2.SourceSpecHashAnnotationKey] = hash
102-
return true, nil
82+
if export.Annotations[kubebindv1alpha2.SourceSpecHashAnnotationKey] != hashOfHashes {
83+
// both exist, update APIServiceExport
84+
logger.V(1).Info("Updating APIServiceExport. Hash mismatch", "hash", hashOfHashes, "expected", export.Annotations[kubebindv1alpha2.SourceSpecHashAnnotationKey])
85+
if export.Annotations == nil {
86+
export.Annotations = map[string]string{}
10387
}
104-
105-
conditions.MarkTrue(export, kubebindv1alpha2.APIServiceExportConditionProviderInSync)
88+
export.Annotations[kubebindv1alpha2.SourceSpecHashAnnotationKey] = hashOfHashes
89+
return true, nil
10690
}
10791

92+
conditions.MarkTrue(export, kubebindv1alpha2.APIServiceExportConditionProviderInSync)
93+
10894
return false, nil
10995
}
96+
97+
func hashOfHashes(hashes []string) string {
98+
hexHashes := append([]string{}, hashes...)
99+
100+
sort.Strings(hexHashes)
101+
102+
rootHasher := sha256.New()
103+
for _, h := range hexHashes {
104+
rootHasher.Write([]byte(h))
105+
}
106+
return hex.EncodeToString(rootHasher.Sum(nil))
107+
}

contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func NewController(
5656
serviceExportRequestInformer bindinformers.APIServiceExportRequestInformer,
5757
serviceExportInformer bindinformers.APIServiceExportInformer,
5858
crdInformer apiextensionsinformers.CustomResourceDefinitionInformer,
59+
apiResourceSchemaInformer bindinformers.APIResourceSchemaInformer,
5960
) (*Controller, error) {
6061
queue := workqueue.NewTypedRateLimitingQueueWithConfig(workqueue.DefaultTypedControllerRateLimiter[string](), workqueue.TypedRateLimitingQueueConfig[string]{Name: controllerName})
6162

@@ -88,12 +89,18 @@ func NewController(
8889
crdLister: crdInformer.Lister(),
8990
crdIndexer: crdInformer.Informer().GetIndexer(),
9091

92+
apiResourceSchemaLister: apiResourceSchemaInformer.Lister(),
93+
apiResourceSchemaIndexer: apiResourceSchemaInformer.Informer().GetIndexer(),
94+
9195
reconciler: reconciler{
9296
informerScope: scope,
9397
clusterScopedIsolation: isolation,
9498
getCRD: func(name string) (*apiextensionsv1.CustomResourceDefinition, error) {
9599
return crdInformer.Lister().Get(name)
96100
},
101+
getAPIResourceSchema: func(ctx context.Context, name string) (*kubebindv1alpha2.APIResourceSchema, error) {
102+
return apiResourceSchemaInformer.Lister().Get(name)
103+
},
97104
getServiceExport: func(ns, name string) (*kubebindv1alpha2.APIServiceExport, error) {
98105
return serviceExportInformer.Lister().APIServiceExports(ns).Get(name)
99106
},
@@ -186,6 +193,9 @@ type Controller struct {
186193
crdLister apiextensionslisters.CustomResourceDefinitionLister
187194
crdIndexer cache.Indexer
188195

196+
apiResourceSchemaLister bindlisters.APIResourceSchemaLister
197+
apiResourceSchemaIndexer cache.Indexer
198+
189199
reconciler
190200

191201
commit CommitFunc

0 commit comments

Comments
 (0)