From 229009bd3caf8f6d396ad2dd028fab8506adda3f Mon Sep 17 00:00:00 2001 From: joe Date: Sun, 19 Jul 2026 02:43:07 +0800 Subject: [PATCH] init: go api with CI/CD --- .gitea/workflows/build.yml | 50 ++++++++++++++++ Dockerfile | 14 +++++ deploy/helm/Chart.yaml | 9 +++ deploy/helm/templates/deployment.yaml | 85 +++++++++++++++++++++++++++ deploy/helm/values.yaml | 38 ++++++++++++ go.mod | 3 + main.go | 44 ++++++++++++++ 7 files changed, 243 insertions(+) create mode 100644 .gitea/workflows/build.yml create mode 100644 Dockerfile create mode 100644 deploy/helm/Chart.yaml create mode 100644 deploy/helm/templates/deployment.yaml create mode 100644 deploy/helm/values.yaml create mode 100644 go.mod create mode 100644 main.go diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..1f6f111 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,50 @@ +name: Build & Deploy Go API + +on: + push: + branches: [main] + +env: + REGISTRY: zot-registry.cicd.svc.cluster.local:5000 + +jobs: + build: + runs-on: self-hosted + steps: + - name: Install build tools + run: apk add --no-cache curl git docker-cli 2>/dev/null || true + + - name: Clone + prepare + run: | + WORK=/root/.cache/demo-go-api + mkdir -p $WORK + cd $WORK + [ -d .git ] || git clone http://joe:${GITEA_TOKEN}@192.168.1.212:3000/joe/demo-go-api.git . + git pull + + - name: Build & push image + run: | + cd /root/.cache/demo-go-api + SHORT=${GITHUB_SHA::8} + TAG="${REGISTRY}/demo-go-api:${SHORT}" + LATEST="${REGISTRY}/demo-go-api:latest" + docker build --build-arg VERSION=${SHORT} -t "${TAG}" -t "${LATEST}" . + docker push "${TAG}" + docker push "${LATEST}" + echo "✓ Pushed ${TAG}" + env: + GITHUB_SHA: ${{ github.sha }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + + - name: Patch ArgoCD + run: | + SHORT=${GITHUB_SHA::8} + if kubectl -n argocd get application demo-go-api &>/dev/null; then + kubectl -n argocd patch application demo-go-api --type merge \ + -p "{\"spec\":{\"source\":{\"helm\":{\"parameters\":[{\"name\":\"image.tag\",\"value\":\"${SHORT}\"}]}}}}" + echo "✓ ArgoCD patched" + else + echo "⚠ demo-go-api Application not yet created" + fi + env: + GITHUB_SHA: ${{ github.sha }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9c2420b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.23-alpine AS builder +WORKDIR /app +COPY go.mod ./ +RUN go mod download +COPY . . +ARG VERSION=dev +RUN CGO_ENABLED=0 go build -ldflags="-s -w -X main.VERSION=${VERSION}" -o /out/api . + +FROM gcr.io/distroless/static-debian12:nonroot +WORKDIR /app +COPY --from=builder /out/api /app/api +USER nonroot:nonroot +EXPOSE 8080 +ENTRYPOINT ["/app/api"] diff --git a/deploy/helm/Chart.yaml b/deploy/helm/Chart.yaml new file mode 100644 index 0000000..1665476 --- /dev/null +++ b/deploy/helm/Chart.yaml @@ -0,0 +1,9 @@ +# Minimal Helm chart for any service +# Place at: /deploy/helm/ + +apiVersion: v2 +name: demo-go-api +description: Standard Helm chart for k3s services +type: application +version: 0.1.0 +appVersion: "1.0.0" \ No newline at end of file diff --git a/deploy/helm/templates/deployment.yaml b/deploy/helm/templates/deployment.yaml new file mode 100644 index 0000000..c663ca0 --- /dev/null +++ b/deploy/helm/templates/deployment.yaml @@ -0,0 +1,85 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Chart.Name }} + labels: + app: {{ .Chart.Name }} + app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app: {{ .Chart.Name }} + template: + metadata: + labels: + app: {{ .Chart.Name }} + spec: + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.registry }}/{{ .Values.image.name }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + {{- if .Values.probes.liveness.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.probes.liveness.path }} + port: {{ .Values.probes.liveness.port }} + initialDelaySeconds: 30 + periodSeconds: 10 + {{- end }} + {{- if .Values.probes.readiness.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.probes.readiness.path }} + port: {{ .Values.probes.readiness.port }} + initialDelaySeconds: 5 + periodSeconds: 5 + {{- end }} + env: + {{- toYaml .Values.env | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} +spec: + type: {{ .Values.service.type }} + selector: + app: {{ .Chart.Name }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http +{{- if .Values.ingress.enabled }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Chart.Name }} + annotations: + {{- if .Values.ingress.className }} + kubernetes.io/ingress.class: {{ .Values.ingress.className }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + rules: + - host: {{ .Values.ingress.host }} + http: + paths: + - path: {{ .Values.ingress.path | default "/" }} + pathType: Prefix + backend: + service: + name: {{ .Chart.Name }} + port: + number: {{ .Values.service.port }} +{{- end }} \ No newline at end of file diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml new file mode 100644 index 0000000..1926832 --- /dev/null +++ b/deploy/helm/values.yaml @@ -0,0 +1,38 @@ +# Default values +image: + registry: 127.0.0.1:5000 + name: demo-go-api + tag: latest + pullPolicy: Always + +replicaCount: 2 + +service: + type: ClusterIP + port: 8080 + +ingress: + enabled: false + className: traefik + host: demo-go-api.local + path: / + +resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + +env: [] + +probes: + liveness: + enabled: true + path: /healthz + port: 8080 + readiness: + enabled: true + path: /healthz + port: 8080 \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7e62ed2 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/joe/demo-go-api + +go 1.23 diff --git a/main.go b/main.go new file mode 100644 index 0000000..de9a057 --- /dev/null +++ b/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "encoding/json" + "log" + "net/http" + "os" + "time" +) + +type Response struct { + Service string `json:"service"` + Message string `json:"message"` + Time string `json:"time"` + Hostname string `json:"hostname"` + Version string `json:"version"` +} + +func main() { + port := os.Getenv("PORT") + if port == "" { + port = "8080" + } + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + hostname, _ := os.Hostname() + resp := Response{ + Service: "demo-go-api", + Message: "Hello from Go API!", + Time: time.Now().Format(time.RFC3339), + Hostname: hostname, + Version: os.Getenv("VERSION"), + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) + }) + + http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("ok")) + }) + + log.Printf("Listening on :%s", port) + log.Fatal(http.ListenAndServe(":"+port, nil)) +}