Skip to content

Commit 27e0b4c

Browse files
authored
Add helm deployment & fix generator (#347)
* Add helm deployment & fixup generator * add helm deployment --------- Signed-off-by: Mangirdas Judeikis <mangirdas@judeikis.lt> On-behalf-of: @SAP mangirdas.judeikis@sap.com
1 parent 0745d25 commit 27e0b4c

40 files changed

Lines changed: 4064 additions & 476 deletions

.github/workflows/image.yaml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ jobs:
2828
- uses: sigstore/cosign-installer@v3.7.0
2929
- name: Install ko
3030
run: go install github.com/google/ko@latest
31+
32+
- name: Install Helm
33+
uses: azure/setup-helm@v3
34+
with:
35+
version: 'v3.12.0'
3136

3237
- name: Set LDFLAGS
3338
run: echo LDFLAGS="$(make ldflags)" | tee -a >> $GITHUB_ENV
@@ -64,6 +69,42 @@ jobs:
6469
-a run_id=${{ github.run_id }} \
6570
-a run_attempt=${{ github.run_attempt }}
6671
72+
- name: Package and push Helm charts as OCI
73+
env:
74+
HELM_EXPERIMENTAL_OCI: 1
75+
run: |
76+
# Login to GitHub Container Registry for Helm
77+
echo "${{ github.token }}" | helm registry login ghcr.io --username ${{ github.actor }} --password-stdin
78+
79+
# Set chart version - use tag name if available, otherwise use semver format
80+
if [[ "${{ github.ref_type }}" == "tag" ]]; then
81+
CHART_VERSION="${{ github.ref_name }}"
82+
# Remove 'v' prefix if present
83+
CHART_VERSION="${CHART_VERSION#v}"
84+
else
85+
CHART_VERSION="0.0.0-${{ github.sha }}"
86+
fi
87+
88+
# Package and push each chart in deploy/charts/
89+
for chart_dir in deploy/charts/*/; do
90+
if [ -f "${chart_dir}Chart.yaml" ]; then
91+
chart_name=$(basename "$chart_dir")
92+
echo "Processing chart: $chart_name"
93+
94+
# Update chart version and appVersion in Chart.yaml
95+
sed -i "s/^version:.*/version: ${CHART_VERSION}/" "${chart_dir}Chart.yaml"
96+
sed -i "s/^appVersion:.*/appVersion: ${CHART_VERSION}/" "${chart_dir}Chart.yaml"
97+
98+
# Package the chart
99+
helm package "$chart_dir" --version "${CHART_VERSION}"
100+
101+
# Push to GitHub Container Registry
102+
helm push "${chart_name}-${CHART_VERSION}.tgz" "oci://ghcr.io/${{ github.repository_owner }}/charts"
103+
104+
echo "Helm chart pushed to oci://ghcr.io/${{ github.repository_owner }}/charts/${chart_name}:${CHART_VERSION}"
105+
fi
106+
done
107+
67108
- uses: actions/delete-package-versions@v3
68109
with:
69110
package-name: 'kube-bind'

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ coverage.*
1414
/dex
1515
/bin
1616
docs/generators/cli-doc/cli-doc
17-
dex/
1817
apiserviceexport.yaml
18+
*.prod

Makefile

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ GOBIN_DIR=$(abspath ./bin )
2424
PATH := $(GOBIN_DIR):$(TOOLS_GOBIN_DIR):$(PATH)
2525
TMPDIR := $(shell mktemp -d)
2626

27+
# Image build configuration
28+
# REV is the short git sha of latest commit.
29+
REV ?= $(shell git rev-parse --short HEAD)
30+
IMAGE_REPO ?= kube-bind
31+
2732
# Detect the path used for the install target
2833
ifeq (,$(shell go env GOBIN))
2934
INSTALL_GOBIN=$(shell go env GOPATH)/bin
@@ -290,6 +295,7 @@ CONTRIBS_E2E := $(patsubst %,test-e2e-contrib-%,$(CONTRIBS))
290295

291296
.PHONY: test-e2e-contribs $(CONTRIBS_E2E)
292297
test-e2e-contribs: $(CONTRIBS_E2E) ## Run e2e tests for external integrations
298+
293299
test-e2e-contrib-kcp: $(DEX) $(KCP)
294300
$(CONTRIBS_E2E):
295301
cd contrib/$(patsubst test-e2e-contrib-%,%,$@) && $(GO_TEST) -race -count $(COUNT) $(E2E_PARALLELISM_FLAG) ./test/e2e/...
@@ -369,32 +375,29 @@ deploy-docs: venv ## Deploy docs
369375
. $(VENV)/activate; \
370376
REMOTE=$(REMOTE) BRANCH=$(BRANCH) docs/scripts/deploy-docs.sh
371377

372-
# Image build configuration
373-
# REV is the short git sha of latest commit.
374-
REV=$(shell git rev-parse --short HEAD)
375-
KIND_CLUSTER ?= backend
376-
KO_DOCKER_REPO ?= kube-bind
377-
378+
# Example: make IMAGE_REPO=ghcr.io/<username> image-local
378379
.PHONY: image-local
379380
image-local:
380381
@echo "Building images locally with tag $(REV)"
381382
@command -v ko >/dev/null 2>&1 || { echo "ko not found. Install with: go install github.com/google/ko@latest"; exit 1; }
382383

383384
@echo "Building konnector image locally..."
384-
KO_DOCKER_REPO=$(KO_DOCKER_REPO) ko build \
385+
KO_DOCKER_REPO=$(IMAGE_REPO) ko build \
385386
--local \
386387
-B \
387388
-t $(REV) \
388389
./cmd/konnector
389390

390391
@echo "Building backend image locally..."
391-
KO_DOCKER_REPO=$(KO_DOCKER_REPO) ko build \
392+
KO_DOCKER_REPO=$(IMAGE_REPO) ko build \
392393
--local \
393394
-B \
394395
-t $(REV) \
395396
./cmd/backend
396397

397-
@echo "Successfully built local images with tag $(REV)"
398+
@echo "Successfully built local images:"
399+
@echo " $(IMAGE_REPO)/konnector:$(REV)"
400+
@echo " $(IMAGE_REPO)/backend:$(REV)"
398401

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

409+
.PHONY: helm-build-local
410+
helm-build-local: ## Build and package Helm charts locally for testing
411+
@echo "Building Helm charts locally..."
412+
@command -v helm >/dev/null 2>&1 || { echo "helm not found. Install from: https://helm.sh/docs/intro/install/"; exit 1; }
413+
414+
@# Set chart version to semver format for local builds (0.0.0-<git-sha>)
415+
CHART_VERSION="0.0.0-$(REV)"; \
416+
for chart_dir in deploy/charts/*/; do \
417+
if [ -f "$${chart_dir}Chart.yaml" ]; then \
418+
chart_name=$$(basename "$$chart_dir"); \
419+
echo "Processing chart: $$chart_name"; \
420+
\
421+
cp "$${chart_dir}Chart.yaml" "$${chart_dir}Chart.yaml.bak"; \
422+
sed -i.tmp "s/^version:.*/version: $$CHART_VERSION/" "$${chart_dir}Chart.yaml"; \
423+
sed -i.tmp "s/^appVersion:.*/appVersion: $$CHART_VERSION/" "$${chart_dir}Chart.yaml"; \
424+
rm -f "$${chart_dir}Chart.yaml.tmp"; \
425+
\
426+
helm package "$$chart_dir" --version "$$CHART_VERSION" --destination ./bin/; \
427+
echo "Packaged: ./bin/$$chart_name-$$CHART_VERSION.tgz"; \
428+
\
429+
mv "$${chart_dir}Chart.yaml.bak" "$${chart_dir}Chart.yaml"; \
430+
fi; \
431+
done
432+
@echo "Helm charts built successfully in ./bin/"
433+
434+
.PHONY: helm-clean
435+
helm-clean: ## Clean up built helm charts
436+
rm -f ./bin/*.tgz
437+
438+
.PHONY: helm-push-local
439+
helm-push-local: ## Push Helm charts to IMAGE_REPO registry
440+
@echo "Pushing Helm charts to registry: $(IMAGE_REPO)"
441+
@command -v helm >/dev/null 2>&1 || { echo "helm not found. Install from: https://helm.sh/docs/intro/install/"; exit 1; }
442+
443+
CHART_VERSION="0.0.0-$(REV)"; \
444+
export HELM_EXPERIMENTAL_OCI=1; \
445+
for chart_file in ./bin/*-$$CHART_VERSION.tgz; do \
446+
if [ -f "$$chart_file" ]; then \
447+
chart_filename=$$(basename "$$chart_file"); \
448+
chart_name=$${chart_filename%-$$CHART_VERSION.tgz}; \
449+
if [[ "$$chart_name" =~ [[:space:]] ]]; then \
450+
echo "Skipping chart with invalid name: '$$chart_name' (contains spaces)"; \
451+
continue; \
452+
fi; \
453+
echo "Pushing $$chart_name to $(IMAGE_REPO)"; \
454+
helm push "$$chart_file" "oci://$(IMAGE_REPO)/charts"; \
455+
echo "Chart available at: oci://$(IMAGE_REPO)/charts/$$chart_name:$$CHART_VERSION"; \
456+
fi; \
457+
done
458+
459+
.PHONY: helm-test
460+
helm-test: helm-build-local ## Test Helm chart installation (dry-run)
461+
@echo "Testing Helm chart installation..."
462+
CHART_VERSION="0.0.0-$(REV)"; \
463+
for chart_dir in deploy/charts/*/; do \
464+
if [ -f "$${chart_dir}Chart.yaml" ]; then \
465+
chart_name=$$(basename "$$chart_dir"); \
466+
echo "Testing chart: $$chart_name"; \
467+
helm install test-$$chart_name "./bin/$$chart_name-$$CHART_VERSION.tgz" --dry-run --debug; \
468+
echo "✓ Chart $$chart_name passes dry-run test"; \
469+
fi; \
470+
done
471+
406472
include Makefile.venv

backend/controllers/clusterbinding/clusterbinding_controller.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,12 @@ func NewClusterBindingReconciler(
147147
return r, nil
148148
}
149149

150-
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=clusterbindings,verbs=get;list;watch;create;update;patch;delete
151-
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=clusterbindings/status,verbs=get;update;patch
152-
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=clusterbindings/finalizers,verbs=update
153-
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports,verbs=get;list;watch
150+
//+kubebuilder:rbac:groups=kube-bind.io,resources=clusterbindings,verbs=get;list;watch;create;update;patch;delete
151+
//+kubebuilder:rbac:groups=kube-bind.io,resources=clusterbindings/status,verbs=get;update;patch
152+
//+kubebuilder:rbac:groups=kube-bind.io,resources=clusterbindings/finalizers,verbs=update
153+
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiserviceexports,verbs=get;list;watch
154+
//+kubebuilder:rbac:groups=kube-bind.io,resources=collections,verbs=get;list;watch
155+
//+kubebuilder:rbac:groups=kube-bind.io,resources=apiserviceexporttemplates,verbs=get;list;watch
154156
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles,verbs=get;list;watch;create;update;patch;delete
155157
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings,verbs=get;list;watch;create;update;patch;delete
156158
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete

backend/controllers/rbac.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright 2022 The Kube Bind Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package controllers
18+
19+
// This is Core access needed for backend controllers.
20+
//+kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch;create;update;patch;delete
21+
//+kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete
22+
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
23+
//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete
24+
//+kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch;create;update;patch;delete
25+
26+
// Additional RBAC permissions for export functionality
27+
// These permissions allow the backend to grant RBAC permissions for exported resources
28+
//+kubebuilder:rbac:groups="",resources=configmaps,verbs=*
29+
//+kubebuilder:rbac:groups="",resources=secrets,verbs=*
30+
31+
// Wildcard permissions to allow granting RBAC permissions for any API group/resource
32+
// This is needed for kube-bind to create ClusterRoles with permissions for bound resources
33+
// In a way this makes all the above specific permissions redundant, but they are left for clarity and traceability.
34+
//+kubebuilder:rbac:groups=*,resources=*,verbs=*

backend/controllers/serviceexport/serviceexport_controller.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,11 @@ func NewAPIServiceExportReconciler(
8282
return r, nil
8383
}
8484

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

9191
// Reconcile is part of the main kubernetes reconciliation loop which aims to
9292
// move the current state of the cluster closer to the desired state.

backend/controllers/serviceexportrequest/serviceexportrequest_controller.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,12 @@ func getBoundSchemaMapper(clusterName string, cl cluster.Cluster) handler.TypedE
177177
})
178178
}
179179

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

187187
// Reconcile is part of the main kubernetes reconciliation loop which aims to
188188
// move the current state of the cluster closer to the desired state.

backend/controllers/servicenamespace/servicenamespace_controller.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,11 @@ func getServiceExportMapper(clusterName string, cl cluster.Cluster) handler.Type
206206
})
207207
}
208208

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

backend/http/handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ func (h *handler) handleAuthorize(w http.ResponseWriter, r *http.Request) {
205205
ProviderClusterID: providerCluster, // used in multicluster-runtime providers
206206
}
207207
if callbackPort != "" && code.RedirectURL == "" {
208-
code.RedirectURL = fmt.Sprintf("http://localhost:%s/callback", callbackPort)
208+
code.RedirectURL = fmt.Sprintf("http://127.0.0.1:%s/callback", callbackPort)
209209
}
210210

211211
if code.RedirectURL == "" || code.SessionID == "" || code.ClusterID == "" {

backend/template/resources.gohtml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<!-- Bootstrap Icons -->
1111
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css">
1212

13-
<title>Modules - Kube Bind</title>
13+
<title>Services - Kube Bind</title>
1414
<style>
1515
body {
1616
background: #f8f9fa;
@@ -36,25 +36,25 @@
3636
margin: 0;
3737
}
3838

39-
.module-card {
39+
.service-card {
4040
border: 1px solid #dee2e6;
4141
border-radius: 6px;
4242
background: white;
4343
transition: border-color 0.15s ease-in-out;
4444
margin-bottom: 1rem;
4545
}
4646

47-
.module-card:hover {
47+
.service-card:hover {
4848
border-color: #007bff;
4949
}
5050

51-
.module-card .card-header {
51+
.service-card .card-header {
5252
background: #f8f9fa;
5353
border-bottom: 1px solid #dee2e6;
5454
padding: 0.75rem 1rem;
5555
}
5656

57-
.module-card .card-header h5 {
57+
.service-card .card-header h5 {
5858
margin: 0;
5959
font-size: 1.1rem;
6060
font-weight: 500;
@@ -135,14 +135,14 @@
135135
<body>
136136
<div class="container">
137137
<div class="page-header">
138-
<h2>Available Modules</h2>
139-
<p>Select a module to bind its resources and permissions</p>
138+
<h2>Available Services</h2>
139+
<p>Select a service to bind its resources and permissions</p>
140140
</div>
141141

142142
<div class="row">
143-
{{range $moduleIdx, $schema := .Schemas}}
143+
{{range $serviceIdx, $schema := .Schemas}}
144144
<div class="col-lg-6 col-md-12 mb-3">
145-
<div class="card module-card">
145+
<div class="card service-card">
146146
<div class="card-header">
147147
<h5>{{$schema.Name}}</h5>
148148
{{if $schema.Description}}<small class="text-muted">{{$schema.Description}}</small>{{end}}
@@ -171,10 +171,10 @@
171171
</span>
172172
{{if or $claim.Selector.NamedResources $claim.Selector.LabelSelector}}
173173
<br>
174-
<a class="btn details-btn mt-1" data-toggle="collapse" href="#claim-{{$moduleIdx}}-{{$i}}" role="button" aria-expanded="false">
174+
<a class="btn details-btn mt-1" data-toggle="collapse" href="#claim-{{$serviceIdx}}-{{$i}}" role="button" aria-expanded="false">
175175
Details
176176
</a>
177-
<div class="collapse mt-2" id="claim-{{$moduleIdx}}-{{$i}}">
177+
<div class="collapse mt-2" id="claim-{{$serviceIdx}}-{{$i}}">
178178
{{if $claim.Selector.NamedResources}}
179179
<div class="detail-card p-2 mb-2">
180180
<strong>Named:</strong>
@@ -217,7 +217,7 @@
217217
</ul>
218218
<div class="card-body">
219219
<a href="{{if $.Cluster}}/clusters/{{$.Cluster}}{{end}}/bind?s={{$schema.SessionID}}&template={{$schema.Name}}" class="btn bind-btn {{$schema.Name}}">
220-
Bind Module
220+
Bind Service
221221
</a>
222222
</div>
223223
</div>

0 commit comments

Comments
 (0)