Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 6 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ ldflags:
require-%:
@if ! command -v $* 1> /dev/null 2>&1; then echo "$* not found in \$$PATH"; exit 1; fi

build: WHAT ?= ./cmd/... ./cli/cmd/...
build: WHAT ?= ./cmd/... ./cli/cmd/... ./kcp/cmd/kcp-init/...
build: require-jq require-go require-git verify-go-versions ## Build the project
mkdir -p $(GOBIN_DIR)
set -x; for W in $(WHAT); do \
Expand All @@ -132,15 +132,12 @@ build: require-jq require-go require-git verify-go-versions ## Build the project
done
.PHONY: build

.PHONY: build-all
build-all:
GOOS=$(OS) GOARCH=$(ARCH) $(MAKE) build WHAT=./cmd/...

install: WHAT ?= ./cmd/... ./cli/cmd/...
install: ## install binaries to GOBIN
GOOS=$(OS) GOARCH=$(ARCH) go install -ldflags="$(LDFLAGS)" $(WHAT)
.PHONY: install


$(GOLANGCI_LINT):
GOBIN=$(TOOLS_GOBIN_DIR) $(GO_INSTALL) github.com/golangci/golangci-lint/v2/cmd/golangci-lint $(GOLANGCI_LINT_BIN) $(GOLANGCI_LINT_VER)

Expand All @@ -166,7 +163,7 @@ vendor: ## Vendor the dependencies
go mod vendor
.PHONY: vendor

tools: $(GOLANGCI_LINT) $(CONTROLLER_GEN) $(YAML_PATCH) $(GOTESTSUM) $(CODE_GENERATOR)
tools: $(GOLANGCI_LINT) $(CONTROLLER_GEN) $(YAML_PATCH) $(GOTESTSUM) $(CODE_GENERATOR)
.PHONY: tools

$(CONTROLLER_GEN):
Expand All @@ -188,7 +185,7 @@ $(KUBE_APPLYCONFIGURATION_GEN):
GOBIN=$(GOBIN_DIR) $(GO_INSTALL) k8s.io/code-generator/cmd/$(KUBE_APPLYCONFIGURATION_GEN_BIN) $(KUBE_APPLYCONFIGURATION_GEN_BIN) $(KUBE_APPLYCONFIGURATION_GEN_VER)


codegen: WHAT ?= ./sdk/kcp ./sdk/client
codegen: WHAT ?= ./sdk/client
codegen: $(CONTROLLER_GEN) $(YAML_PATCH) $(CODE_GENERATOR) $(KUBE_CLIENT_GEN) $(KUBE_LISTER_GEN) $(KUBE_INFORMER_GEN) $(KUBE_APPLYCONFIGURATION_GEN)
go mod download
./hack/update-codegen.sh
Expand Down Expand Up @@ -273,7 +270,7 @@ endif
test-e2e: TEST_ARGS ?=
test-e2e: WORK_DIR ?= .
test-e2e: WHAT ?= ./test/e2e...
test-e2e: $(KCP) $(DEX) build-all
test-e2e: $(KCP) $(DEX) build
mkdir .kcp
$(DEX) serve hack/dex-config-dev.yaml 2>&1 & DEX_PID=$$!; \
$(KCP) start &>.kcp/kcp.log & KCP_PID=$$!; \
Expand All @@ -288,7 +285,7 @@ endif
test: WHAT ?= ./...
# We will need to move into the sub package, of pkg/apis to run those tests.
test: ## run unit tests
$(GO_TEST) -race -count $(COUNT) -coverprofile=coverage.txt -covermode=atomic $(TEST_ARGS) $$(go list "$(WHAT)" | grep -v ./test/e2e/)
$(GO_TEST) -race -count $(COUNT) -coverprofile=coverage.txt -covermode=atomic $(TEST_ARGS) $$(go list "$(WHAT)" | grep -v ./test/e2e/ | grep -v ./kcp)
cd sdk/apis && $(GO_TEST) -race -count $(COUNT) -coverprofile=coverage.txt -covermode=atomic $(TEST_ARGS) $(WHAT)

.PHONY: verify-imports
Expand Down
158 changes: 158 additions & 0 deletions backend/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
Copyright 2022 The Kube Bind 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 backend

import (
"context"
"fmt"
"net/url"
"strings"

apisv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1"
apisv1alpha2 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2"
"github.com/kcp-dev/multicluster-provider/apiexport"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
ctrlconfig "sigs.k8s.io/controller-runtime/pkg/config"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager"
"sigs.k8s.io/multicluster-runtime/pkg/multicluster"

kuberesources "github.com/kube-bind/kube-bind/backend/kubernetes/resources"
"github.com/kube-bind/kube-bind/backend/options"
kubebindv1alpha1 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha1"
kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2"
)

type Config struct {
Options *options.CompletedOptions

Provider multicluster.Provider
ExternalAddressGenerator kuberesources.ExternalAddreesGeneratorFunc
Manager mcmanager.Manager
Scheme *runtime.Scheme

ClientConfig *rest.Config
}

func NewConfig(options *options.CompletedOptions) (*Config, error) {
config := &Config{
Options: options,
}

// create clients
rules := clientcmd.NewDefaultClientConfigLoadingRules()
rules.ExplicitPath = options.KubeConfig
var err error
config.ClientConfig, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, nil).ClientConfig()
if err != nil {
return nil, err
}
config.ClientConfig = rest.CopyConfig(config.ClientConfig)
config.ClientConfig = rest.AddUserAgent(config.ClientConfig, "kube-bind-backend")

if options.ServerURL != "" {
config.ClientConfig.Host = options.ServerURL
}

// Set up controller-runtime manager
scheme := runtime.NewScheme()
if err := clientgoscheme.AddToScheme(scheme); err != nil {
return nil, fmt.Errorf("error adding client-go scheme: %w", err)
}
if err := apiextensionsv1.AddToScheme(scheme); err != nil {
return nil, fmt.Errorf("error adding apiextensions scheme: %w", err)
}
if err := kubebindv1alpha1.AddToScheme(scheme); err != nil {
return nil, fmt.Errorf("error adding kubebind scheme: %w", err)
}
if err := kubebindv1alpha2.AddToScheme(scheme); err != nil {
return nil, fmt.Errorf("error adding kubebind scheme: %w", err)
}

config.Scheme = scheme

switch options.Provider {
case "kcp":
if err := apisv1alpha1.AddToScheme(scheme); err != nil {
return nil, fmt.Errorf("error adding apis scheme: %w", err)
}
if err := apisv1alpha2.AddToScheme(scheme); err != nil {
return nil, fmt.Errorf("error adding apis scheme: %w", err)
}
provider, err := apiexport.New(config.ClientConfig, apiexport.Options{
Scheme: scheme,
})
if err != nil {
return nil, fmt.Errorf("error setting up kcp provider: %w", err)
}

config.ExternalAddressGenerator = parseKCPServerURL
config.Provider = provider
default:
config.ExternalAddressGenerator = kuberesources.DefaultExternalAddreesGenerator
config.Provider = nil
}

opts := ctrl.Options{
Controller: ctrlconfig.Controller{
SkipNameValidation: ptr.To(config.Options.ExtraOptions.TestingSkipNameValidation),
},
Metrics: metricsserver.Options{
BindAddress: "0",
},
Scheme: scheme,
}

manager, err := mcmanager.New(config.ClientConfig, config.Provider, opts)
if err != nil {
return nil, fmt.Errorf("error setting up controller manager: %w", err)
}

config.Manager = manager

return config, nil
}

func parseKCPServerURL(ctx context.Context, clusterConfig *rest.Config) (string, error) {
// In kcp case, we are talking via apiexport so clientconfig will be pointing to
// https://192.168.2.166:6443/services/apiexport/2sssrgg8ivlpw0cy/kube-bind.io/clusters/2p0rtkf7b697s6mj
// We need to extract host and /clusters/... part
u, err := url.Parse(clusterConfig.Host)
if err != nil {
return "", err
}

// Extract cluster ID from the path
// Path format: /services/apiexport/{export-id}/kube-bind.io/clusters/{cluster-id}
pathParts := strings.Split(strings.Trim(u.Path, "/"), "/")
if len(pathParts) < 5 || pathParts[4] != "clusters" {
return "", fmt.Errorf("invalid apiexport URL format")
}

clusterID := pathParts[5]

// Construct new URL with cluster path
u.Path = "/clusters/" + clusterID

return u.String(), nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/cluster"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
Expand All @@ -39,40 +39,31 @@ import (
mcreconcile "sigs.k8s.io/multicluster-runtime/pkg/reconcile"

kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2"
bindclient "github.com/kube-bind/kube-bind/sdk/client/clientset/versioned"
)

const (
controllerName = "kube-bind-example-backend-clusterbinding"
controllerName = "kube-bind-backend-clusterbinding"
)

// ClusterBindingReconciler reconciles a ClusterBinding object.
type ClusterBindingReconciler struct {
manager mcmanager.Manager

manager mcmanager.Manager
opts controller.TypedOptions[mcreconcile.Request]
scope kubebindv1alpha2.InformerScope
bindClient bindclient.Interface
reconciler reconciler
}

// NewClusterBindingReconciler returns a new ClusterBindingReconciler to reconcile ClusterBindings.
func NewClusterBindingReconciler(
_ context.Context,
mgr mcmanager.Manager,
config *rest.Config,
opts controller.TypedOptions[mcreconcile.Request],
scope kubebindv1alpha2.InformerScope,
) (*ClusterBindingReconciler, error) {
config = rest.CopyConfig(config)
config = rest.AddUserAgent(config, controllerName)

bindClient, err := bindclient.NewForConfig(config)
if err != nil {
return nil, err
}

r := &ClusterBindingReconciler{
manager: mgr,
scope: scope,
bindClient: bindClient,
manager: mgr,
opts: opts,
scope: scope,
reconciler: reconciler{
scope: scope,
listServiceExports: func(ctx context.Context, cache cache.Cache, ns string) ([]*kubebindv1alpha2.APIServiceExport, error) {
Expand All @@ -88,7 +79,7 @@ func NewClusterBindingReconciler(
},
getAPIResourceSchema: func(ctx context.Context, cache cache.Cache, name string) (*kubebindv1alpha2.APIResourceSchema, error) {
result := &kubebindv1alpha2.APIResourceSchema{}
err = cache.Get(ctx, types.NamespacedName{Name: name}, result)
err := cache.Get(ctx, types.NamespacedName{Name: name}, result)
if err != nil {
return nil, fmt.Errorf("failed to get APIResourceSchema %q: %w", name, err)
}
Expand All @@ -102,17 +93,11 @@ func NewClusterBindingReconciler(
}
return &role, nil
},
createClusterRole: func(ctx context.Context, client client.Client, binding *rbacv1.ClusterRole) (*rbacv1.ClusterRole, error) {
if err := client.Create(ctx, binding); err != nil {
return nil, err
}
return binding, nil
createClusterRole: func(ctx context.Context, client client.Client, binding *rbacv1.ClusterRole) error {
return client.Create(ctx, binding)
},
updateClusterRole: func(ctx context.Context, client client.Client, binding *rbacv1.ClusterRole) (*rbacv1.ClusterRole, error) {
if err := client.Update(ctx, binding); err != nil {
return nil, err
}
return binding, nil
updateClusterRole: func(ctx context.Context, client client.Client, binding *rbacv1.ClusterRole) error {
return client.Update(ctx, binding)
},
getClusterRoleBinding: func(ctx context.Context, cache cache.Cache, name string) (*rbacv1.ClusterRoleBinding, error) {
var binding rbacv1.ClusterRoleBinding
Expand All @@ -122,17 +107,11 @@ func NewClusterBindingReconciler(
}
return &binding, nil
},
createClusterRoleBinding: func(ctx context.Context, client client.Client, binding *rbacv1.ClusterRoleBinding) (*rbacv1.ClusterRoleBinding, error) {
if err := client.Create(ctx, binding); err != nil {
return nil, err
}
return binding, nil
createClusterRoleBinding: func(ctx context.Context, client client.Client, binding *rbacv1.ClusterRoleBinding) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do all of these still offer a real value, now that they are one-liners?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just gives consistency in mocking. If one of these are more complex than one-line it makes sense to have all of them same so its just consistent.

We can always refactor these out if we see need. Now - it just change for sake of change :)

return client.Create(ctx, binding)
},
updateClusterRoleBinding: func(ctx context.Context, client client.Client, binding *rbacv1.ClusterRoleBinding) (*rbacv1.ClusterRoleBinding, error) {
if err := client.Update(ctx, binding); err != nil {
return nil, err
}
return binding, nil
updateClusterRoleBinding: func(ctx context.Context, client client.Client, binding *rbacv1.ClusterRoleBinding) error {
return client.Update(ctx, binding)
},
deleteClusterRoleBinding: func(ctx context.Context, client client.Client, name string) error {
binding := &rbacv1.ClusterRoleBinding{
Expand All @@ -144,29 +123,21 @@ func NewClusterBindingReconciler(
var ns v1.Namespace
key := types.NamespacedName{Name: name}
if err := cache.Get(ctx, key, &ns); err != nil {
return nil, fmt.Errorf("failed to get Namespace %q: %w", name, err)
return nil, err
}
return &ns, nil
},
createRoleBinding: func(ctx context.Context, client client.Client, ns string, binding *rbacv1.RoleBinding) (*rbacv1.RoleBinding, error) {
binding.Namespace = ns
if err := client.Create(ctx, binding); err != nil {
return nil, err
}
return binding, nil
createRoleBinding: func(ctx context.Context, client client.Client, binding *rbacv1.RoleBinding) error {
return client.Create(ctx, binding)
},
updateRoleBinding: func(ctx context.Context, client client.Client, ns string, binding *rbacv1.RoleBinding) (*rbacv1.RoleBinding, error) {
binding.Namespace = ns
if err := client.Update(ctx, binding); err != nil {
return nil, err
}
return binding, nil
updateRoleBinding: func(ctx context.Context, client client.Client, binding *rbacv1.RoleBinding) error {
return client.Update(ctx, binding)
},
getRoleBinding: func(ctx context.Context, cache cache.Cache, ns, name string) (*rbacv1.RoleBinding, error) {
var binding rbacv1.RoleBinding
key := types.NamespacedName{Namespace: ns, Name: name}
if err := cache.Get(ctx, key, &binding); err != nil {
return nil, fmt.Errorf("failed to get RoleBinding %q in namespace %q: %w", name, ns, err)
return nil, err
}
return &binding, nil
},
Expand Down Expand Up @@ -244,6 +215,7 @@ func (r *ClusterBindingReconciler) SetupWithManager(mgr mcmanager.Manager) error
&kubebindv1alpha2.APIServiceExport{},
mapAPIResourceSchema,
).
WithOptions(r.opts).
Named(controllerName).
Complete(r)
}
Expand Down
Loading