diff --git a/.github/workflows/lint-helm.yml b/.github/workflows/lint-helm.yml new file mode 100644 index 0000000..98aa394 --- /dev/null +++ b/.github/workflows/lint-helm.yml @@ -0,0 +1,29 @@ +name: Lint Helm Chart + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + lint-helm: + runs-on: ubuntu-latest + env: + CHART_PATH: charts/open5gs-operator + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Helm + uses: azure/setup-helm@v4 + with: + version: v3.18.4 + + - name: Lint Helm chart + run: helm lint $CHART_PATH + + - name: Update Helm dependencies + run: helm dep update $CHART_PATH \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..8077da3 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,93 @@ +name: Build and Publish Docker Image & Helm Chart + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build-and-publish: + runs-on: ubuntu-latest + env: + IMAGE_NAME: gradiant/open5gs-operator + CHART_PATH: charts/open5gs-operator + CHART_REPO: oci://registry-1.docker.io/gradiantcharts + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract version from Makefile + id: version + run: | + VERSION=$(grep -E '^VERSION[[:space:]]*\?=' Makefile | head -1 | sed 's/.*= *//') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Build Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: false + tags: ${{ env.IMAGE_NAME }}:latest,${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }} + + - name: Push Docker image + if: github.event_name == 'push' + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ env.IMAGE_NAME }}:latest,${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }} + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + + - name: Install Helm + uses: azure/setup-helm@v4 + with: + version: v3.18.4 + + - name: Lint Helm chart + run: helm lint $CHART_PATH + + - name: Update Helm dependencies + run: helm dep update $CHART_PATH + + - name: Generate Helm chart + run: | + make helm + + - name: Package Helm chart + run: | + helm package $CHART_PATH --destination packaged-charts + + - name: Helm registry login (DockerHub) + if: github.event_name == 'push' + run: | + echo ${{ secrets.DOCKERHUB_TOKEN }} | helm registry login registry-1.docker.io -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin + + - name: Push Helm chart to DockerHub OCI registry + if: github.event_name == 'push' + run: | + helm push packaged-charts/open5gs-operator-${{ steps.version.outputs.version }}.tgz $CHART_REPO + + - name: Upload Helm chart as artifact + uses: actions/upload-artifact@v4 + with: + name: open5gs-operator-chart + path: packaged-charts/*.tgz \ No newline at end of file diff --git a/README.md b/README.md index 2457db9..dc8a1b2 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ The operator provides full management of Open5GS subscribers, including configur ## How to Install -To install by using Helm, you can use the Helm chart provided in the `charts` directory or the open5gs-operator-1.0.1.tgz file. The chart is also available in the Gradiant Charts repository. +To install by using Helm, you can use the Helm chart provided in the `charts` directory or the open5gs-operator-1.0.4.tgz file. The chart is also available in the Gradiant Charts repository. ```bash -helm install open5gs-operator oci://registry-1.docker.io/gradiantcharts/open5gs-operator --version 1.0.1 +helm install open5gs-operator oci://registry-1.docker.io/gradiantcharts/open5gs-operator --version 1.0.4 ``` To uninstall the operator, run: @@ -112,4 +112,20 @@ A complete demo with UERANSIM is available at [this link](https://gradiant.githu 7. The `webuiImage` field in the CR specifies the version of the Open5GS WebUI image. If not specified, the operator defaults to version `docker.io/gradiant/open5gs-webui:2.7.5`. 8. The `mongoDBVersion` field in the CR specifies the version of the MongoDB image. If not specified, the operator defaults to version `bitnami/mongodb:8.0.6-debian-12-r0`. 9. Components with metric support can generate a `ServiceMonitor` CR to expose metrics to Prometheus. However, ensure that the `ServiceMonitor` CRD is installed in the cluster; otherwise, the operator will encounter an error and fail to create the resource. To create a ServiceMonitor, set the `serviceMonitor` field to `true` in the CR for the desired component. +10. **UPF Deployment Annotations:** The annotations for the UPF Deployment are managed exclusively through the `upf.deploymentAnnotations` field in the CR. Any annotation not present in this field will be automatically reconciled by the operator (added or removed as needed), so manual changes to annotations will not persist unless reflected in the CR. +11. **UPF GTP-U Interface:** The GTP-U network interface used by the UPF is set via the `upf.gtpuDev` field in the CR (e.g., `gtpuDev: "eth0"`). By default, the UPF uses the `eth0` interface. + +## How to create a new release + +To publish a new version of the operator, follow these steps: + +1. Make the necessary changes in the code. +2. Run the version script: + ```bash + ./set-version.sh + ``` + This will update the version in all relevant files (Makefile, kustomization.yaml, Chart.yaml, and values.yaml). +3. Commit the changes and create the corresponding Pull Request (PR). + +This ensures that the published version is consistent with the code and manifests. diff --git a/api/v1/open5gs_types.go b/api/v1/open5gs_types.go index 8f80522..d55a834 100644 --- a/api/v1/open5gs_types.go +++ b/api/v1/open5gs_types.go @@ -46,11 +46,13 @@ type Open5GSSlice struct { } type Open5GSFunction struct { - Enabled *bool `json:"enabled,omitempty" default:"true"` - ServiceAccount *bool `json:"serviceAccount,omitempty" default:"false"` - Metrics *bool `json:"metrics,omitempty" default:"true"` - ServiceMonitor *bool `json:"serviceMonitor,omitempty" default:"false"` - Service []Open5GSService `json:"service,omitempty" default:"{\"name\":\"\",\"port\":0,\"serviceType\":\"\"}"` + Enabled *bool `json:"enabled,omitempty" default:"true"` + ServiceAccount *bool `json:"serviceAccount,omitempty" default:"false"` + Metrics *bool `json:"metrics,omitempty" default:"true"` + ServiceMonitor *bool `json:"serviceMonitor,omitempty" default:"false"` + Service []Open5GSService `json:"service,omitempty" default:"{\"name\":\"\",\"port\":0,\"serviceType\":\"\"}"` + GTPUDev string `json:"gtpuDev,omitempty" default:"eth0"` + DeploymentAnnotations map[string]string `json:"deploymentAnnotations,omitempty"` } type Open5GSService struct { diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 119b35b..7ce0290 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -89,6 +89,13 @@ func (in *Open5GSFunction) DeepCopyInto(out *Open5GSFunction) { *out = make([]Open5GSService, len(*in)) copy(*out, *in) } + if in.DeploymentAnnotations != nil { + in, out := &in.DeploymentAnnotations, &out.DeploymentAnnotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Open5GSFunction. diff --git a/charts/open5gs-operator/Chart.yaml b/charts/open5gs-operator/Chart.yaml index 645c51e..c97990e 100644 --- a/charts/open5gs-operator/Chart.yaml +++ b/charts/open5gs-operator/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: open5gs-operator description: A Helm chart for Kubernetes type: application -version: 1.0.3 -appVersion: "1.0.3" +version: 1.0.4 +appVersion: "1.0.4" diff --git a/charts/open5gs-operator/templates/open5gs-crd.yaml b/charts/open5gs-operator/templates/open5gs-crd.yaml index 05d9a31..a67ee79 100644 --- a/charts/open5gs-operator/templates/open5gs-crd.yaml +++ b/charts/open5gs-operator/templates/open5gs-crd.yaml @@ -42,8 +42,14 @@ spec: properties: amf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -62,8 +68,14 @@ spec: type: object ausf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -82,8 +94,14 @@ spec: type: object bsf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -124,8 +142,14 @@ spec: type: object mongoDB: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -146,8 +170,14 @@ spec: type: string nrf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -166,8 +196,14 @@ spec: type: object nssf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -188,8 +224,14 @@ spec: type: string pcf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -208,8 +250,14 @@ spec: type: object scp: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -228,8 +276,14 @@ spec: type: object smf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -248,8 +302,14 @@ spec: type: object udm: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -268,8 +328,14 @@ spec: type: object udr: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -288,8 +354,14 @@ spec: type: object upf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -308,8 +380,14 @@ spec: type: object webui: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: diff --git a/charts/open5gs-operator/values.yaml b/charts/open5gs-operator/values.yaml index ca27f80..49485b1 100644 --- a/charts/open5gs-operator/values.yaml +++ b/charts/open5gs-operator/values.yaml @@ -9,7 +9,7 @@ controllerManager: - ALL image: repository: gradiant/open5gs-operator - tag: 1.0.3 + tag: 1.0.4 resources: requests: cpu: 100m diff --git a/config/crd/bases/net.gradiant.org_open5gses.yaml b/config/crd/bases/net.gradiant.org_open5gses.yaml index ae9d2a3..c6edaad 100644 --- a/config/crd/bases/net.gradiant.org_open5gses.yaml +++ b/config/crd/bases/net.gradiant.org_open5gses.yaml @@ -41,8 +41,14 @@ spec: properties: amf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -61,8 +67,14 @@ spec: type: object ausf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -81,8 +93,14 @@ spec: type: object bsf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -123,8 +141,14 @@ spec: type: object mongoDB: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -145,8 +169,14 @@ spec: type: string nrf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -165,8 +195,14 @@ spec: type: object nssf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -187,8 +223,14 @@ spec: type: string pcf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -207,8 +249,14 @@ spec: type: object scp: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -227,8 +275,14 @@ spec: type: object smf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -247,8 +301,14 @@ spec: type: object udm: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -267,8 +327,14 @@ spec: type: object udr: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -287,8 +353,14 @@ spec: type: object upf: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: @@ -307,8 +379,14 @@ spec: type: object webui: properties: + deploymentAnnotations: + additionalProperties: + type: string + type: object enabled: type: boolean + gtpuDev: + type: string metrics: type: boolean service: diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index c3550ec..1aa8145 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: gradiant/open5gs-operator - newTag: 1.0.1 + newTag: 1.0.4 diff --git a/config/samples/net_v1_open5gs.yaml b/config/samples/net_v1_open5gs.yaml index 9283f3f..808a3a6 100644 --- a/config/samples/net_v1_open5gs.yaml +++ b/config/samples/net_v1_open5gs.yaml @@ -60,6 +60,9 @@ spec: metrics: true serviceMonitor: true serviceAccount: true + deploymentAnnotations: + k8s.v1.cni.cncf.io/networks: upf-dataplane-5 + gtpuDev: "eth0" service: - name: pfcp serviceType: ClusterIP diff --git a/internal/controller/open5gs_controller.go b/internal/controller/open5gs_controller.go index ab68162..77330c8 100644 --- a/internal/controller/open5gs_controller.go +++ b/internal/controller/open5gs_controller.go @@ -754,7 +754,8 @@ func (r *Open5GSReconciler) reconcileUDR(ctx context.Context, req ctrl.Request, func (r *Open5GSReconciler) reconcileUPF(ctx context.Context, req ctrl.Request, open5gs *netv1.Open5GS, logger logr.Logger) error { componentName := "UPF" - configMap := CreateUPFConfigMap(req.Namespace, open5gs.Name, open5gs.Spec.Configuration, *open5gs.Spec.UPF.Metrics) + gtpuDev := open5gs.Spec.UPF.GTPUDev + configMap := CreateUPFConfigMap(req.Namespace, open5gs.Name, open5gs.Spec.Configuration, *open5gs.Spec.UPF.Metrics, gtpuDev) entrypointConfigMap := CreateUPFEntrypointConfigMap(req.Namespace, open5gs.Name) envVars := []corev1.EnvVar{} @@ -794,7 +795,7 @@ func (r *Open5GSReconciler) reconcileUPF(ctx context.Context, req ctrl.Request, serviceAccount = CreateServiceAccount(req.Namespace, open5gs.Name, componentName) serviceAccountName = serviceAccount.Name } - deployment := CreateUPFDeployment(req.Namespace, open5gs.Name, open5gs.Spec.Open5GSImage, envVars, *open5gs.Spec.UPF.Metrics, serviceAccountName) + deployment := CreateUPFDeployment(req.Namespace, open5gs.Name, open5gs.Spec.Open5GSImage, envVars, *open5gs.Spec.UPF.Metrics, serviceAccountName, open5gs.Spec.UPF.DeploymentAnnotations) return r.reconcileComponent(ctx, open5gs, componentName, logger, configMap, deployment, services, serviceMonitor, serviceAccount) } @@ -990,6 +991,52 @@ func reconcileDeployment(ctx context.Context, r *Open5GSReconciler, open5gs *net return nil } + upfDeploymentName := open5gs.Name + "-upf" + if foundDeployment.Name == upfDeploymentName { + allowedAnnotations := map[string]struct{}{"open5gs/configmap-hash": {}} + if open5gs.Spec.UPF.DeploymentAnnotations != nil { + for k := range open5gs.Spec.UPF.DeploymentAnnotations { + allowedAnnotations[k] = struct{}{} + } + } + + newAnnotations := make(map[string]string) + newAnnotations["open5gs/configmap-hash"] = configMapHash + if open5gs.Spec.UPF.DeploymentAnnotations != nil { + for k, v := range open5gs.Spec.UPF.DeploymentAnnotations { + newAnnotations[k] = v + } + } + + current := foundDeployment.Spec.Template.Annotations + needUpdate := false + if len(current) != len(newAnnotations) { + needUpdate = true + } else { + for k, v := range newAnnotations { + if current[k] != v { + needUpdate = true + break + } + } + for k := range current { + if _, ok := newAnnotations[k]; !ok { + needUpdate = true + break + } + } + } + if needUpdate { + foundDeployment.Spec.Template.Annotations = newAnnotations + if err := r.Client.Update(ctx, foundDeployment); err != nil { + logger.Error(err, "Failed to update UPF Deployment annotations", "component", componentName) + return err + } + logger.Info("UPF Deployment annotations reconciled", "component", componentName) + } + return nil + } + if !deploymentEqual(deployment, foundDeployment) || foundDeployment.Spec.Template.Annotations["open5gs/configmap-hash"] != configMapHash { foundDeployment.Spec = deployment.Spec foundDeployment.Spec.Template.Annotations["open5gs/configmap-hash"] = configMapHash @@ -1316,6 +1363,9 @@ func setDefaultValues(open5gs *netv1.Open5GS) { defaultWebUIServiceAccount := false open5gs.Spec.WebUI.ServiceAccount = &defaultWebUIServiceAccount } + if open5gs.Spec.UPF.GTPUDev == "" { + open5gs.Spec.UPF.GTPUDev = "eth0" + } } diff --git a/internal/controller/open5gs_resources.go b/internal/controller/open5gs_resources.go index 2efdcf5..da77970 100644 --- a/internal/controller/open5gs_resources.go +++ b/internal/controller/open5gs_resources.go @@ -572,7 +572,7 @@ udr: } } -func CreateUPFConfigMap(namespace, open5gsName string, configuration netv1.Open5GSConfiguration, metrics bool) *corev1.ConfigMap { +func CreateUPFConfigMap(namespace, open5gsName string, configuration netv1.Open5GSConfiguration, metrics bool, gtpuDev string) *corev1.ConfigMap { metricsConfig := ` ` if metrics { @@ -582,6 +582,9 @@ func CreateUPFConfigMap(namespace, open5gsName string, configuration netv1.Open5 - dev: eth0 port: 9090` } + if gtpuDev == "" { + gtpuDev = "eth0" + } return &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: open5gsName + "-upf", @@ -603,8 +606,7 @@ upf: client: gtpu: server: - - dev: eth0` + - metricsConfig + ` + - dev: ` + gtpuDev + metricsConfig + ` session: - dev: ogstun @@ -652,7 +654,7 @@ $@ } } -func CreateUPFDeployment(namespace, open5gsName, image string, envVars []corev1.EnvVar, metrics bool, serviceAccountName string) *appsv1.Deployment { +func CreateUPFDeployment(namespace, open5gsName, image string, envVars []corev1.EnvVar, metrics bool, serviceAccountName string, deploymentAnnotations map[string]string) *appsv1.Deployment { var ports []corev1.ContainerPort if metrics { ports = []corev1.ContainerPort{ @@ -712,6 +714,7 @@ func CreateUPFDeployment(namespace, open5gsName, image string, envVars []corev1. "app.kubernetes.io/instance": open5gsName, "app.kubernetes.io/name": "upf", }, + Annotations: deploymentAnnotations, }, Spec: corev1.PodSpec{ ServiceAccountName: serviceAccountName, diff --git a/open5gs-operator-1.0.3.tgz b/open5gs-operator-1.0.3.tgz deleted file mode 100644 index f5a6870..0000000 Binary files a/open5gs-operator-1.0.3.tgz and /dev/null differ diff --git a/open5gs-operator-1.0.4.tgz b/open5gs-operator-1.0.4.tgz new file mode 100644 index 0000000..71700ac Binary files /dev/null and b/open5gs-operator-1.0.4.tgz differ diff --git a/set-version.sh b/set-version.sh new file mode 100755 index 0000000..c309523 --- /dev/null +++ b/set-version.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Script to update the version in Makefile, kustomization.yaml, Chart.yaml, and values.yaml +# Usage: ./set-version.sh + +set -e + +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +NEW_VERSION="$1" + +# Update Makefile +sed -i "s/^VERSION ?=.*/VERSION ?= $NEW_VERSION/" Makefile + +# Update kustomization.yaml +sed -i "s/newTag: .*/newTag: $NEW_VERSION/" config/manager/kustomization.yaml + +# Update Chart.yaml +sed -i "s/^version: .*/version: $NEW_VERSION/" charts/open5gs-operator/Chart.yaml +sed -i "s/^appVersion: .*/appVersion: \"$NEW_VERSION\"/" charts/open5gs-operator/Chart.yaml + +# Update values.yaml (regardless of indentation) +sed -i "s/^\s*tag: .*/ tag: $NEW_VERSION/" charts/open5gs-operator/values.yaml + +echo "Versions updated to $NEW_VERSION!" \ No newline at end of file diff --git a/test/suite_1/suite_1_test.go b/test/suite_1/suite_1_test.go index 8188de1..f607f22 100644 --- a/test/suite_1/suite_1_test.go +++ b/test/suite_1/suite_1_test.go @@ -44,7 +44,7 @@ var _ = Describe("controller", Ordered, func() { var controllerPodName string var err error - var projectimage = "gradiant/open5gs-operator:1.0.1" + var projectimage = "gradiant/open5gs-operator:1.0.4" // By("building the manager(Operator) image") // cmd := exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectimage))