diff --git a/.github/workflows/image.yaml b/.github/workflows/image.yaml index 79b55e78c..230234eea 100644 --- a/.github/workflows/image.yaml +++ b/.github/workflows/image.yaml @@ -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 @@ -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' diff --git a/.gitignore b/.gitignore index f0d1aa73d..cde76d088 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,5 @@ coverage.* /dex /bin docs/generators/cli-doc/cli-doc -dex/ apiserviceexport.yaml +*.prod \ No newline at end of file diff --git a/Makefile b/Makefile index e30a4f474..3796fd6a8 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -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/... @@ -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/ 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: @@ -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-) + 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 diff --git a/backend/controllers/clusterbinding/clusterbinding_controller.go b/backend/controllers/clusterbinding/clusterbinding_controller.go index d524d8c54..abd1c7853 100644 --- a/backend/controllers/clusterbinding/clusterbinding_controller.go +++ b/backend/controllers/clusterbinding/clusterbinding_controller.go @@ -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 diff --git a/backend/controllers/rbac.go b/backend/controllers/rbac.go new file mode 100644 index 000000000..49d4d6c88 --- /dev/null +++ b/backend/controllers/rbac.go @@ -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=* diff --git a/backend/controllers/serviceexport/serviceexport_controller.go b/backend/controllers/serviceexport/serviceexport_controller.go index 21166e4ff..ab6124882 100644 --- a/backend/controllers/serviceexport/serviceexport_controller.go +++ b/backend/controllers/serviceexport/serviceexport_controller.go @@ -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. diff --git a/backend/controllers/serviceexportrequest/serviceexportrequest_controller.go b/backend/controllers/serviceexportrequest/serviceexportrequest_controller.go index 4330a7f78..3788d011b 100644 --- a/backend/controllers/serviceexportrequest/serviceexportrequest_controller.go +++ b/backend/controllers/serviceexportrequest/serviceexportrequest_controller.go @@ -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. diff --git a/backend/controllers/servicenamespace/servicenamespace_controller.go b/backend/controllers/servicenamespace/servicenamespace_controller.go index e9f4e52b0..0b28901b9 100644 --- a/backend/controllers/servicenamespace/servicenamespace_controller.go +++ b/backend/controllers/servicenamespace/servicenamespace_controller.go @@ -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 diff --git a/backend/http/handler.go b/backend/http/handler.go index b4a0b1e3a..5fbd16fbf 100644 --- a/backend/http/handler.go +++ b/backend/http/handler.go @@ -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 == "" { diff --git a/backend/template/resources.gohtml b/backend/template/resources.gohtml index 1f653295c..67db1f76a 100644 --- a/backend/template/resources.gohtml +++ b/backend/template/resources.gohtml @@ -10,7 +10,7 @@ - Modules - Kube Bind + Services - Kube Bind