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
41 changes: 41 additions & 0 deletions .github/workflows/image.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ jobs:
- uses: sigstore/cosign-installer@v3.7.0
- name: Install ko
run: go install github.com/google/ko@latest

- name: Install Helm
uses: azure/setup-helm@v3
with:
version: 'v3.12.0'

- name: Set LDFLAGS
run: echo LDFLAGS="$(make ldflags)" | tee -a >> $GITHUB_ENV
Expand Down Expand Up @@ -62,6 +67,42 @@ jobs:
-a run_id=${{ github.run_id }} \
-a run_attempt=${{ github.run_attempt }}

- name: Package and push Helm charts as OCI
env:
HELM_EXPERIMENTAL_OCI: 1
run: |
# Login to GitHub Container Registry for Helm
echo "${{ github.token }}" | helm registry login ghcr.io --username ${{ github.actor }} --password-stdin

# Set chart version - use tag name if available, otherwise use semver format
if [[ "${{ github.ref_type }}" == "tag" ]]; then
CHART_VERSION="${{ github.ref_name }}"
# Remove 'v' prefix if present
CHART_VERSION="${CHART_VERSION#v}"
else
CHART_VERSION="0.0.0-${{ github.sha }}"
fi

# Package and push each chart in deploy/charts/
for chart_dir in deploy/charts/*/; do
if [ -f "${chart_dir}Chart.yaml" ]; then
chart_name=$(basename "$chart_dir")
echo "Processing chart: $chart_name"

# Update chart version and appVersion in Chart.yaml
sed -i "s/^version:.*/version: ${CHART_VERSION}/" "${chart_dir}Chart.yaml"
sed -i "s/^appVersion:.*/appVersion: ${CHART_VERSION}/" "${chart_dir}Chart.yaml"

# Package the chart
helm package "$chart_dir" --version "${CHART_VERSION}"

# Push to GitHub Container Registry
helm push "${chart_name}-${CHART_VERSION}.tgz" "oci://ghcr.io/${{ github.repository_owner }}/charts"

echo "Helm chart pushed to oci://ghcr.io/${{ github.repository_owner }}/charts/${chart_name}:${CHART_VERSION}"
fi
done

- uses: actions/delete-package-versions@v3
with:
package-name: 'kube-bind'
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ coverage.*
/dex
/bin
docs/generators/cli-doc/cli-doc
dex/
apiserviceexport.yaml
*.prod
84 changes: 75 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ GOBIN_DIR=$(abspath ./bin )
PATH := $(GOBIN_DIR):$(TOOLS_GOBIN_DIR):$(PATH)
TMPDIR := $(shell mktemp -d)

# Image build configuration
# REV is the short git sha of latest commit.
REV ?= $(shell git rev-parse --short HEAD)
IMAGE_REPO ?= kube-bind

# Detect the path used for the install target
ifeq (,$(shell go env GOBIN))
INSTALL_GOBIN=$(shell go env GOPATH)/bin
Expand Down Expand Up @@ -290,6 +295,7 @@ CONTRIBS_E2E := $(patsubst %,test-e2e-contrib-%,$(CONTRIBS))

.PHONY: test-e2e-contribs $(CONTRIBS_E2E)
test-e2e-contribs: $(CONTRIBS_E2E) ## Run e2e tests for external integrations

test-e2e-contrib-kcp: $(DEX) $(KCP)
$(CONTRIBS_E2E):
cd contrib/$(patsubst test-e2e-contrib-%,%,$@) && $(GO_TEST) -race -count $(COUNT) $(E2E_PARALLELISM_FLAG) ./test/e2e/...
Expand Down Expand Up @@ -369,32 +375,29 @@ deploy-docs: venv ## Deploy docs
. $(VENV)/activate; \
REMOTE=$(REMOTE) BRANCH=$(BRANCH) docs/scripts/deploy-docs.sh

# Image build configuration
# REV is the short git sha of latest commit.
REV=$(shell git rev-parse --short HEAD)
KIND_CLUSTER ?= backend
KO_DOCKER_REPO ?= kube-bind

# Example: make IMAGE_REPO=ghcr.io/<username> image-local
.PHONY: image-local
image-local:
@echo "Building images locally with tag $(REV)"
@command -v ko >/dev/null 2>&1 || { echo "ko not found. Install with: go install github.com/google/ko@latest"; exit 1; }

@echo "Building konnector image locally..."
KO_DOCKER_REPO=$(KO_DOCKER_REPO) ko build \
KO_DOCKER_REPO=$(IMAGE_REPO) ko build \
--local \
-B \
-t $(REV) \
./cmd/konnector

@echo "Building backend image locally..."
KO_DOCKER_REPO=$(KO_DOCKER_REPO) ko build \
KO_DOCKER_REPO=$(IMAGE_REPO) ko build \
--local \
-B \
-t $(REV) \
./cmd/backend

@echo "Successfully built local images with tag $(REV)"
@echo "Successfully built local images:"
@echo " $(IMAGE_REPO)/konnector:$(REV)"
@echo " $(IMAGE_REPO)/backend:$(REV)"

.PHONY: kind-load
kind-load:
Expand All @@ -403,4 +406,67 @@ kind-load:
kind load docker-image $(KO_DOCKER_REPO)/backend:$(REV) --name $(KIND_CLUSTER)
@echo "Successfully loaded images into kind cluster '$(KIND_CLUSTER)'"

.PHONY: helm-build-local
helm-build-local: ## Build and package Helm charts locally for testing
@echo "Building Helm charts locally..."
@command -v helm >/dev/null 2>&1 || { echo "helm not found. Install from: https://helm.sh/docs/intro/install/"; exit 1; }

@# Set chart version to semver format for local builds (0.0.0-<git-sha>)
CHART_VERSION="0.0.0-$(REV)"; \
for chart_dir in deploy/charts/*/; do \
if [ -f "$${chart_dir}Chart.yaml" ]; then \
chart_name=$$(basename "$$chart_dir"); \
echo "Processing chart: $$chart_name"; \
\
cp "$${chart_dir}Chart.yaml" "$${chart_dir}Chart.yaml.bak"; \
sed -i.tmp "s/^version:.*/version: $$CHART_VERSION/" "$${chart_dir}Chart.yaml"; \
sed -i.tmp "s/^appVersion:.*/appVersion: $$CHART_VERSION/" "$${chart_dir}Chart.yaml"; \
rm -f "$${chart_dir}Chart.yaml.tmp"; \
\
helm package "$$chart_dir" --version "$$CHART_VERSION" --destination ./bin/; \
echo "Packaged: ./bin/$$chart_name-$$CHART_VERSION.tgz"; \
\
mv "$${chart_dir}Chart.yaml.bak" "$${chart_dir}Chart.yaml"; \
fi; \
done
@echo "Helm charts built successfully in ./bin/"

.PHONY: helm-clean
helm-clean: ## Clean up built helm charts
rm -f ./bin/*.tgz

.PHONY: helm-push-local
helm-push-local: ## Push Helm charts to IMAGE_REPO registry
@echo "Pushing Helm charts to registry: $(IMAGE_REPO)"
@command -v helm >/dev/null 2>&1 || { echo "helm not found. Install from: https://helm.sh/docs/intro/install/"; exit 1; }

CHART_VERSION="0.0.0-$(REV)"; \
export HELM_EXPERIMENTAL_OCI=1; \
for chart_file in ./bin/*-$$CHART_VERSION.tgz; do \
if [ -f "$$chart_file" ]; then \
chart_filename=$$(basename "$$chart_file"); \
chart_name=$${chart_filename%-$$CHART_VERSION.tgz}; \
if [[ "$$chart_name" =~ [[:space:]] ]]; then \
echo "Skipping chart with invalid name: '$$chart_name' (contains spaces)"; \
continue; \
fi; \
echo "Pushing $$chart_name to $(IMAGE_REPO)"; \
helm push "$$chart_file" "oci://$(IMAGE_REPO)/charts"; \
echo "Chart available at: oci://$(IMAGE_REPO)/charts/$$chart_name:$$CHART_VERSION"; \
fi; \
done

.PHONY: helm-test
helm-test: helm-build-local ## Test Helm chart installation (dry-run)
@echo "Testing Helm chart installation..."
CHART_VERSION="0.0.0-$(REV)"; \
for chart_dir in deploy/charts/*/; do \
if [ -f "$${chart_dir}Chart.yaml" ]; then \
chart_name=$$(basename "$$chart_dir"); \
echo "Testing chart: $$chart_name"; \
helm install test-$$chart_name "./bin/$$chart_name-$$CHART_VERSION.tgz" --dry-run --debug; \
echo "✓ Chart $$chart_name passes dry-run test"; \
fi; \
done

include Makefile.venv
10 changes: 6 additions & 4 deletions backend/controllers/clusterbinding/clusterbinding_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,12 @@ func NewClusterBindingReconciler(
return r, nil
}

//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=clusterbindings,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=clusterbindings/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=clusterbindings/finalizers,verbs=update
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports,verbs=get;list;watch
//+kubebuilder:rbac:groups=kube-bind.io,resources=clusterbindings,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kube-bind.io,resources=clusterbindings/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=kube-bind.io,resources=clusterbindings/finalizers,verbs=update
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiserviceexports,verbs=get;list;watch
//+kubebuilder:rbac:groups=kube-bind.io,resources=collections,verbs=get;list;watch
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiserviceexporttemplates,verbs=get;list;watch
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete
Expand Down
34 changes: 34 additions & 0 deletions backend/controllers/rbac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
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 controllers

// This is Core access needed for backend controllers.
//+kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch;create;update;patch;delete

// Additional RBAC permissions for export functionality
// These permissions allow the backend to grant RBAC permissions for exported resources
//+kubebuilder:rbac:groups="",resources=configmaps,verbs=*
//+kubebuilder:rbac:groups="",resources=secrets,verbs=*

// Wildcard permissions to allow granting RBAC permissions for any API group/resource
// This is needed for kube-bind to create ClusterRoles with permissions for bound resources
// In a way this makes all the above specific permissions redundant, but they are left for clarity and traceability.
//+kubebuilder:rbac:groups=*,resources=*,verbs=*
10 changes: 5 additions & 5 deletions backend/controllers/serviceexport/serviceexport_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@ func NewAPIServiceExportReconciler(
return r, nil
}

//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports/finalizers,verbs=update
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=boundschemas,verbs=get;list;watch
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=boundschemas/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiserviceexports,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiserviceexports/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiserviceexports/finalizers,verbs=update
//+kubebuilder:rbac:groups=kube-bind.io,resources=boundschemas,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kube-bind.io,resources=boundschemas/status,verbs=get;update;patch;list

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,12 @@ func getBoundSchemaMapper(clusterName string, cl cluster.Cluster) handler.TypedE
})
}

//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexportrequests,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexportrequests/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexportrequests/finalizers,verbs=update
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiresourceschemas,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=resources=apiservicenamespaces,verbs=get;list;watch;create
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiserviceexportrequests,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiserviceexportrequests/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiserviceexportrequests/finalizers,verbs=update
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiserviceexports,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiresourceschemas,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiservicenamespaces,verbs=get;list;watch;create

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,11 @@ func getServiceExportMapper(clusterName string, cl cluster.Cluster) handler.Type
})
}

//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiservicenamespaces,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiservicenamespaces/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiservicenamespaces/finalizers,verbs=update
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=clusterbindings,verbs=get;list;watch
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports,verbs=get;list;watch
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiservicenamespaces,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiservicenamespaces/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiservicenamespaces/finalizers,verbs=update
//+kubebuilder:rbac:groups=kube-bind.io,resources=clusterbindings,verbs=get;list;watch
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiserviceexports,verbs=get;list;watch
//+kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete

Expand Down
2 changes: 1 addition & 1 deletion backend/http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ func (h *handler) handleAuthorize(w http.ResponseWriter, r *http.Request) {
ProviderClusterID: providerCluster, // used in multicluster-runtime providers
}
if callbackPort != "" && code.RedirectURL == "" {
code.RedirectURL = fmt.Sprintf("http://localhost:%s/callback", callbackPort)
code.RedirectURL = fmt.Sprintf("http://127.0.0.1:%s/callback", callbackPort)
}

if code.RedirectURL == "" || code.SessionID == "" || code.ClusterID == "" {
Expand Down
24 changes: 12 additions & 12 deletions backend/template/resources.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css">

<title>Modules - Kube Bind</title>
<title>Services - Kube Bind</title>
<style>
body {
background: #f8f9fa;
Expand All @@ -36,25 +36,25 @@
margin: 0;
}

.module-card {
.service-card {
border: 1px solid #dee2e6;
border-radius: 6px;
background: white;
transition: border-color 0.15s ease-in-out;
margin-bottom: 1rem;
}

.module-card:hover {
.service-card:hover {
border-color: #007bff;
}

.module-card .card-header {
.service-card .card-header {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 0.75rem 1rem;
}

.module-card .card-header h5 {
.service-card .card-header h5 {
margin: 0;
font-size: 1.1rem;
font-weight: 500;
Expand Down Expand Up @@ -135,14 +135,14 @@
<body>
<div class="container">
<div class="page-header">
<h2>Available Modules</h2>
<p>Select a module to bind its resources and permissions</p>
<h2>Available Services</h2>
<p>Select a service to bind its resources and permissions</p>
</div>

<div class="row">
{{range $moduleIdx, $schema := .Schemas}}
{{range $serviceIdx, $schema := .Schemas}}
<div class="col-lg-6 col-md-12 mb-3">
<div class="card module-card">
<div class="card service-card">
<div class="card-header">
<h5>{{$schema.Name}}</h5>
{{if $schema.Description}}<small class="text-muted">{{$schema.Description}}</small>{{end}}
Expand Down Expand Up @@ -171,10 +171,10 @@
</span>
{{if or $claim.Selector.NamedResources $claim.Selector.LabelSelector}}
<br>
<a class="btn details-btn mt-1" data-toggle="collapse" href="#claim-{{$moduleIdx}}-{{$i}}" role="button" aria-expanded="false">
<a class="btn details-btn mt-1" data-toggle="collapse" href="#claim-{{$serviceIdx}}-{{$i}}" role="button" aria-expanded="false">
Details
</a>
<div class="collapse mt-2" id="claim-{{$moduleIdx}}-{{$i}}">
<div class="collapse mt-2" id="claim-{{$serviceIdx}}-{{$i}}">
{{if $claim.Selector.NamedResources}}
<div class="detail-card p-2 mb-2">
<strong>Named:</strong>
Expand Down Expand Up @@ -217,7 +217,7 @@
</ul>
<div class="card-body">
<a href="{{if $.Cluster}}/clusters/{{$.Cluster}}{{end}}/bind?s={{$schema.SessionID}}&template={{$schema.Name}}" class="btn bind-btn {{$schema.Name}}">
Bind Module
Bind Service
</a>
</div>
</div>
Expand Down
Loading