diff --git a/.gitignore b/.gitignore index b09f8c5..262116e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,12 @@ # dev out.yaml -# keys and certs -tls.key -tls.crt -ssh.key - # node node_modules # helm -users.yaml \ No newline at end of file +helm-charts/code-editor/assets + +# keys +*.cert +*.key diff --git a/charts/code-editor/templates/configmaps.yaml b/charts/code-editor/templates/configmaps.yaml deleted file mode 100644 index 6bdec72..0000000 --- a/charts/code-editor/templates/configmaps.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: code-editor-users -data: - users: | - users: - {{- range .Values.users }} - - name: {{ .name }} - password: {{ .password | quote }} - path: {{ .path }} - {{- end }} -{{- if $.Values.codeServer.vscodeSettings.enable }} ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: vscode-config -data: - settings.json: | - {{- .Files.Get "vscode-settings.json" | nindent 4 }} -{{- end }} diff --git a/charts/code-editor/templates/deployment.yaml b/charts/code-editor/templates/deployment.yaml deleted file mode 100644 index b4223a9..0000000 --- a/charts/code-editor/templates/deployment.yaml +++ /dev/null @@ -1,259 +0,0 @@ -{{- range $s := .Values.users }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: code-editor-{{ $s.name }} - labels: - app.kubernetes.io/name: {{ include "code-editor.name" $ }} - helm.sh/chart: {{ include "code-editor.chart" $ }} - app.kubernetes.io/instance: {{ $.Release.Name }} - app.kubernetes.io/managed-by: {{ $.Release.Service }} - app.code-editor/path: {{ $s.path }} -spec: - replicas: {{ $.Values.codeServer.replicas }} - strategy: - type: Recreate - selector: - matchLabels: - app.kubernetes.io/name: {{ include "code-editor.name" $ }} - app.kubernetes.io/instance: {{ $.Release.Name }} - app.code-editor/path: {{ $s.path }} - template: - metadata: - labels: - app.kubernetes.io/name: {{ include "code-editor.name" $ }} - app.kubernetes.io/instance: {{ $.Release.Name }} - app.code-editor/path: {{ $s.path }} - spec: - imagePullSecrets: {{- toYaml $.Values.codeServer.imagePullSecrets | nindent 8 }} - {{- if $.Values.codeServer.hostnameOverride }} - hostname: {{ $.Values.codeServer.hostnameOverride }} - {{- end }} - {{- if $.Values.codeServer.priorityClassName }} - priorityClassName: {{ $.Values.codeServer.priorityClassName }} - {{- end }} - {{- if $.Values.codeServer.securityContext.enabled }} - securityContext: - fsGroup: {{ $.Values.codeServer.securityContext.fsGroup }} - {{- end }} - initContainers: - - name : ssh - image: {{ $.Values.ssh.image.repository }}:{{ $.Values.ssh.image.tag }} - command: ['/bin/sh', '-c', "cp /ssh/id_ed25519 /etc/ssh-key/id_ed25519 && ssh-keyscan -t ed25519 github.com >> /etc/ssh-key/known_hosts && chown -R 1000:1000 /etc/ssh-key && chmod 700 /etc/ssh-key/id_ed25519"] - volumeMounts: - - name: ssh - mountPath: "/ssh" - - name: ssh-root - mountPath: "/etc/ssh-key" - - name: gitconfig - image: busybox - command: ['/bin/sh', '-c', "chown -R 1000:1000 /etc && echo '[user]\n\temail = francesco.torchia@suse.com\n\tname = Francesco Torchia' > /home/coder/.gitconfig"] - volumeMounts: - - name: cfg - mountPath: /home/coder/ - - name: customization - image: {{ $.Values.codeServer.image.repository }}:{{ $.Values.codeServer.image.tag }} - imagePullPolicy: IfNotPresent - env: - - name: SERVICE_URL - value: https://open-vsx.org/vscode/gallery - - name: ITEM_URL - value: https://open-vsx.org/vscode/item - command: - - sh - - -c - - | - code-server --install-extension hoovercj.vscode-power-mode - volumeMounts: - - name: cfg - mountPath: /home/coder - containers: -{{- if $.Values.codeServer.extraContainers }} -{{ tpl $.Values.codeServer.extraContainers . | indent 8}} -{{- end }} - - name: {{ $.Chart.Name }} - image: "{{ $.Values.codeServer.image.repository }}:{{ $.Values.codeServer.image.tag }}" - imagePullPolicy: {{ $.Values.codeServer.image.pullPolicy }} - {{- if $.Values.codeServer.securityContext.enabled }} - securityContext: - runAsUser: {{ $.Values.codeServer.securityContext.runAsUser }} - {{- end }} - {{- if $.Values.codeServer.lifecycle.enabled }} - lifecycle: - {{- if $.Values.codeServer.lifecycle.postStart }} - postStart: - {{ toYaml $.Values.codeServer.lifecycle.postStart | nindent 14 }} - {{- end }} - {{- if $.Values.codeServer.lifecycle.preStop }} - preStop: - {{ toYaml $.Values.codeServer.lifecycle.preStop | nindent 14 }} - {{- end }} - {{- end }} - env: - - name: PASSWORD - value: {{ $s.password | quote }} - {{- if $.Values.codeServer.extraVars }} -{{ toYaml $.Values.codeServer.extraVars | indent 10 }} - {{- end }} - {{- if $.Values.codeServer.extraArgs }} - args: -{{ toYaml $.Values.codeServer.extraArgs | indent 10 }} - {{- end }} - volumeMounts: - {{- if $.Values.codeServer.vscodeSettings.enable }} - - name: vscode-config - mountPath: "/home/coder/.local/share/code-server/User" - {{- end }} - - name: ssh-root - mountPath: "/home/coder/.ssh" - - name: data - mountPath: /git - - name: cfg - mountPath: /home/coder - {{- range $.Values.codeServer.extraConfigmapMounts }} - - name: {{ .name }} - mountPath: {{ .mountPath }} - subPath: {{ .subPath | default "" }} - readOnly: {{ .readOnly }} - {{- end }} - {{- range $.Values.codeServer.extraSecretMounts }} - - name: {{ .name }} - mountPath: {{ .mountPath }} - subPath: {{ .subPath | default "" }} - readOnly: {{ .readOnly }} - {{- end }} - {{- range $.Values.codeServer.extraVolumeMounts }} - - name: {{ .name }} - mountPath: {{ .mountPath }} - subPath: {{ .subPath | default "" }} - readOnly: {{ .readOnly }} - {{- end }} - ports: - - name: http - containerPort: 8080 - protocol: TCP - {{- range $.Values.codeServer.extraPorts }} - - name: {{ .name }} - containerPort: {{ .port }} - protocol: {{ .protocol }} - {{- end }} - livenessProbe: - tcpSocket: - port: 8080 - failureThreshold: 5 - readinessProbe: - tcpSocket: - port: 8080 - failureThreshold: 5 - resources: - {{- toYaml $.Values.codeServer.resources | nindent 12 }} - {{- with $.Values.codeServer.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with $.Values.codeServer.affinity }} - affinity: - {{- tpl . $ | nindent 8 }} - {{- end }} - {{- with $.Values.codeServer.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ template "code-editor.serviceAccountName" $ }} - volumes: - {{- if $.Values.codeServer.vscodeSettings.enable }} - - name: vscode-config - configMap: - name: vscode-config - {{- end }} - - name: ssh - secret: - secretName: secret-ssh-auth - - name: ssh-root - emptyDir: {} - - name: cfg - emptyDir: {} - - name: data - {{- if $.Values.codeServer.persistence.enabled }} - {{- if not $.Values.codeServer.persistence.hostPath }} - persistentVolumeClaim: - claimName: {{ $.Values.codeServer.persistence.existingClaim | default (include "code-editor.fullname" $) }} - {{- else }} - hostPath: - path: {{ $.Values.codeServer.persistence.hostPath }} - type: Directory - {{- end -}} - {{- else }} - emptyDir: {} - {{- end -}} - {{- range $.Values.codeServer.extraSecretMounts }} - - name: {{ .name }} - secret: - secretName: {{ .secretName }} - defaultMode: {{ .defaultMode }} - {{- end }} - {{- range $.Values.codeServer.extraConfigmapMounts }} - - name: {{ .name }} - configMap: - name: {{ .configMap }} - defaultMode: {{ .defaultMode }} - {{- end }} - {{- range $.Values.codeServer.extraVolumeMounts }} - - name: {{ .name }} - {{- if .existingClaim }} - persistentVolumeClaim: - claimName: {{ .existingClaim }} - {{- else }} - hostPath: - path: {{ .hostPath }} - type: Directory - {{- end }} - {{- end }} ---- -{{- end }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: server - labels: - app: server -spec: - replicas: {{ .Values.server.replicas }} - selector: - matchLabels: - app: server - template: - metadata: - labels: - app: server - spec: - serviceAccountName: server - containers: - - env: - - name: PORT - value: {{ quote .Values.server.server.env.port }} - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: API_SECRET - value: {{ quote .Values.server.server.env.api.secret }} - - name: API_TOKEN_EXPIRATION - value: {{ quote .Values.server.server.env.api.tokenExpiration }} - image: {{ .Values.server.server.image.repository }}:{{ .Values.server.server.image.tag - | default .Chart.AppVersion }} - name: server - imagePullPolicy: {{ .Values.server.server.image.pullPolicy }} - resources: {} - volumeMounts: - - name: users - mountPath: "/server/assets" - readOnly: true - volumes: - - name: users - configMap: - name: code-editor-users - items: - - key: users - path: users - diff --git a/charts/code-editor/templates/secrets.yaml b/charts/code-editor/templates/secrets.yaml deleted file mode 100644 index a12bba9..0000000 --- a/charts/code-editor/templates/secrets.yaml +++ /dev/null @@ -1,41 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "code-editor.fullname" . }} - annotations: - "helm.sh/hook": "pre-install" - labels: - app.kubernetes.io/name: {{ include "code-editor.name" . }} - helm.sh/chart: {{ include "code-editor.chart" . }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/managed-by: {{ .Release.Service }} -type: Opaque -data: - {{ if .Values.codeServer.password }} - password: "{{ .Values.codeServer.password | b64enc }}" - {{ else }} - password: "{{ randAlphaNum 24 | b64enc }}" - {{ end }} -{{- if not .Values.dev }} ---- -apiVersion: v1 -data: - tls.crt: |- - {{ .Files.Get "tls.crt" | b64enc }} - tls.key: |- - {{ .Files.Get "tls.key" | b64enc }} -kind: Secret -metadata: - name: minio-tls - namespace: code-editor -type: kubernetes.io/tls -{{- end }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: secret-ssh-auth -type: Opaque -data: - id_ed25519: |- - {{ .Files.Get "ssh.key" | b64enc }} diff --git a/charts/code-editor/templates/service.yaml b/charts/code-editor/templates/service.yaml deleted file mode 100644 index 4df9bd1..0000000 --- a/charts/code-editor/templates/service.yaml +++ /dev/null @@ -1,39 +0,0 @@ -{{- range .Values.users }} -apiVersion: v1 -kind: Service -metadata: - name: code-editor-{{ .name }} - labels: - app.kubernetes.io/name: code-editor-{{ .name }} - helm.sh/chart: code-editor-{{ .name }} - app.kubernetes.io/instance: {{ $.Release.Name }} - app.kubernetes.io/managed-by: {{ $.Release.Service }} -spec: - type: {{ $.Values.codeServer.service.type }} - ports: - - port: {{ $.Values.codeServer.service.port }} - targetPort: http - protocol: TCP - name: http - {{- range $.Values.codeServer.extraPorts }} - - port: {{ .port }} - targetPort: {{ .port }} - protocol: {{ .protocol }} - name: {{ .name }} - {{- end }} - selector: - app.code-editor/path: {{ .path }} ---- -{{- end }} -apiVersion: v1 -kind: Service -metadata: - name: server - labels: - app: server -spec: - type: {{ .Values.server.type }} - selector: - app: server - ports: - {{- .Values.server.ports | toYaml | nindent 2 }} diff --git a/charts/code-editor/.helmignore b/helm-charts/code-editor/.helmignore similarity index 100% rename from charts/code-editor/.helmignore rename to helm-charts/code-editor/.helmignore diff --git a/charts/code-editor/Chart.yaml b/helm-charts/code-editor/Chart.yaml similarity index 100% rename from charts/code-editor/Chart.yaml rename to helm-charts/code-editor/Chart.yaml diff --git a/charts/code-editor/templates/NOTES.txt b/helm-charts/code-editor/templates/NOTES.txt similarity index 100% rename from charts/code-editor/templates/NOTES.txt rename to helm-charts/code-editor/templates/NOTES.txt diff --git a/charts/code-editor/templates/_helpers.tpl b/helm-charts/code-editor/templates/_helpers.tpl similarity index 100% rename from charts/code-editor/templates/_helpers.tpl rename to helm-charts/code-editor/templates/_helpers.tpl diff --git a/charts/code-editor/templates/clusterrolebinding.yaml b/helm-charts/code-editor/templates/clusterrolebinding.yaml similarity index 74% rename from charts/code-editor/templates/clusterrolebinding.yaml rename to helm-charts/code-editor/templates/clusterrolebinding.yaml index 0427d4a..08da97b 100644 --- a/charts/code-editor/templates/clusterrolebinding.yaml +++ b/helm-charts/code-editor/templates/clusterrolebinding.yaml @@ -4,7 +4,10 @@ metadata: name: server rules: - apiGroups: ["", "apps"] - resources: ["pods","pods/exec","services","namespaces","deployments","deployments/scale","jobs"] + resources: ["pods","pods/exec","services","namespaces","deployments","deployments/scale","jobs","secrets"] + verbs: ["get", "update", "create", "watch", "list"] +- apiGroups: ["traefik.containo.us"] + resources: ["ingressroutes"] verbs: ["get", "update", "create", "watch", "list"] --- apiVersion: rbac.authorization.k8s.io/v1 diff --git a/helm-charts/code-editor/templates/configmaps.yaml b/helm-charts/code-editor/templates/configmaps.yaml new file mode 100644 index 0000000..2fe35b6 --- /dev/null +++ b/helm-charts/code-editor/templates/configmaps.yaml @@ -0,0 +1,32 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: code-editor-users +data: + users.yaml: | + users: + {{- $.Values.users | toYaml | nindent 4 }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: code-editor-templates +data: + service.yaml: | + {{- .Files.Get "assets/service.yaml" | nindent 4 }} + traefik-route.yaml: | + {{- .Files.Get "assets/traefik-route.yaml" | nindent 4 }} + secret.yaml: | + {{- .Files.Get "assets/secret.yaml" | nindent 4 }} + deployment.yaml: | + {{- .Files.Get "assets/deployment.yaml" | nindent 4 }} +{{- if $.Values.codeServer.vscodeSettings.enable }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vscode-config +data: + settings.json: | + {{- .Files.Get "assets/vscode-settings.json" | nindent 4 }} +{{- end }} diff --git a/charts/code-editor/templates/daemonset.yaml b/helm-charts/code-editor/templates/daemonset.yaml similarity index 100% rename from charts/code-editor/templates/daemonset.yaml rename to helm-charts/code-editor/templates/daemonset.yaml diff --git a/helm-charts/code-editor/templates/deployment.yaml b/helm-charts/code-editor/templates/deployment.yaml new file mode 100644 index 0000000..0da6dc6 --- /dev/null +++ b/helm-charts/code-editor/templates/deployment.yaml @@ -0,0 +1,60 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: server + labels: + app: server +spec: + replicas: {{ .Values.server.replicas }} + selector: + matchLabels: + app: server + template: + metadata: + labels: + app: server + spec: + serviceAccountName: server + containers: + - env: + - name: PORT + value: {{ quote .Values.server.server.env.port }} + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: API_SECRET + value: {{ quote .Values.server.server.env.api.secret }} + - name: API_TOKEN_EXPIRATION + value: {{ quote .Values.server.server.env.api.tokenExpiration }} + image: {{ .Values.server.server.image.repository }}:{{ .Values.server.server.image.tag + | default .Chart.AppVersion }} + name: server + imagePullPolicy: {{ .Values.server.server.image.pullPolicy }} + resources: {} + volumeMounts: + - name: users + mountPath: "/server/assets/users" + readOnly: true + - name: templates + mountPath: "/server/assets/templates" + readOnly: true + volumes: + - name: users + configMap: + name: code-editor-users + items: + - key: users.yaml + path: users.yaml + - name: templates + configMap: + name: code-editor-templates + items: + - key: service.yaml + path: service.yaml + - key: traefik-route.yaml + path: traefik-route.yaml + - key: secret.yaml + path: secret.yaml + - key: deployment.yaml + path: deployment.yaml diff --git a/charts/code-editor/templates/ingress.yaml b/helm-charts/code-editor/templates/ingress.yaml similarity index 81% rename from charts/code-editor/templates/ingress.yaml rename to helm-charts/code-editor/templates/ingress.yaml index 3615e84..2a8e129 100644 --- a/charts/code-editor/templates/ingress.yaml +++ b/helm-charts/code-editor/templates/ingress.yaml @@ -3,12 +3,9 @@ kind: Middleware metadata: name: strip-prefix spec: - stripPrefix: - prefixes: - - /code-editor/api - {{- range .Values.users }} - - /code-editor/{{ .path }} - {{- end }} + stripPrefixRegex: + regex: + - "/code-editor/[a-zA-Z]+/" --- apiVersion: traefik.containo.us/v1alpha1 kind: Middleware @@ -64,16 +61,7 @@ spec: {{- else }} - websecure {{- end }} - routes: - {{- range .Values.users }} - - match: Host(`{{ $.Values.domain }}`) && PathPrefix(`/code-editor/{{ .path }}/`) - kind: Rule - services: - - name: code-editor-{{ .name }} - port: 8080 - middlewares: - - name: strip-prefix - {{- end }} + routes: [] {{- if not .Values.dev }} tls: secretName: minio-tls diff --git a/charts/code-editor/templates/pvc.yaml b/helm-charts/code-editor/templates/pvc.yaml similarity index 100% rename from charts/code-editor/templates/pvc.yaml rename to helm-charts/code-editor/templates/pvc.yaml diff --git a/helm-charts/code-editor/templates/secrets.yaml b/helm-charts/code-editor/templates/secrets.yaml new file mode 100644 index 0000000..47aa363 --- /dev/null +++ b/helm-charts/code-editor/templates/secrets.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-ssh-auth +type: Opaque +data: + id_ed25519: |- + {{ .Files.Get "assets/ssh.key" | b64enc }} +{{- if not .Values.dev }} +--- +apiVersion: v1 +data: + tls.crt: |- + {{ .Files.Get "assets/tls.crt" | b64enc }} + tls.key: |- + {{ .Files.Get "assets/tls.key" | b64enc }} +kind: Secret +metadata: + name: minio-tls + namespace: code-editor +type: kubernetes.io/tls +{{- end }} diff --git a/helm-charts/code-editor/templates/service.yaml b/helm-charts/code-editor/templates/service.yaml new file mode 100644 index 0000000..37c881b --- /dev/null +++ b/helm-charts/code-editor/templates/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: server + labels: + app: server +spec: + type: {{ .Values.server.type }} + selector: + app: server + ports: + {{- .Values.server.ports | toYaml | nindent 2 }} diff --git a/charts/code-editor/templates/serviceaccount.yaml b/helm-charts/code-editor/templates/serviceaccount.yaml similarity index 100% rename from charts/code-editor/templates/serviceaccount.yaml rename to helm-charts/code-editor/templates/serviceaccount.yaml diff --git a/charts/code-editor/templates/tests/test-connection.yaml b/helm-charts/code-editor/templates/tests/test-connection.yaml similarity index 100% rename from charts/code-editor/templates/tests/test-connection.yaml rename to helm-charts/code-editor/templates/tests/test-connection.yaml diff --git a/charts/code-editor/values.yaml b/helm-charts/code-editor/values.yaml similarity index 100% rename from charts/code-editor/values.yaml rename to helm-charts/code-editor/values.yaml diff --git a/package.json b/package.json index 4283b42..e09fd0a 100644 --- a/package.json +++ b/package.json @@ -7,15 +7,16 @@ "test": "echo \"Error: no test specified\" && exit 1", "cluster:create": "k3d cluster create code-editor -p '80:80@loadbalancer' -p '443:443@loadbalancer'", "cluster:delete": "k3d cluster delete code-editor", - "code-editor:install": "npm run assets:generate && helm install code-editor charts/code-editor -f charts/code-editor/users.yaml -n code-editor --create-namespace", - "code-editor:uninstall": "helm uninstall code-editor -n code-editor", - "code-editor:install:dry-run": "npm run assets:generate && helm install code-editor charts/code-editor -f charts/code-editor/users.yaml -n code-editor --create-namespace --dry-run > out.yaml", - "assets:generate": "npm run users:generate && npm run cert:generate", + "code-editor:install": "npm run assets:generate && helm install code-editor helm-charts/code-editor -n code-editor --create-namespace", + "code-editor:uninstall": "helm uninstall code-editor -n code-editor && kubectl delete all --all -n code-editor --force && kubectl delete secret --all -n code-editor", + "code-editor:install:dry-run": "npm run assets:generate && helm install code-editor helm-charts/code-editor -n code-editor --create-namespace --dry-run > out.yaml", + "assets:generate": "npm run templates:generate && npm run cert:generate", "cert:generate": "sh scripts/generate-certificates.sh", - "users:generate": "sh scripts/generate-users.sh", - "dev:code-editor:install": "npm run dev:assets:generate && helm install code-editor charts/code-editor -f charts/code-editor/users.yaml --set dev=true -n code-editor --create-namespace", - "dev:code-editor:install:dry-run": "npm run dev:assets:generate && helm install code-editor charts/code-editor -f charts/code-editor/users.yaml --set dev=true -n code-editor --create-namespace --dry-run > out.yaml", - "dev:assets:generate": "npm run dev:users:generate && npm run cert:generate", + "templates:generate": "sh scripts/generate-templates.sh", + "dev:code-editor:install": "npm run dev:assets:generate && helm install code-editor helm-charts/code-editor --set dev=true -n code-editor --create-namespace", + "dev:code-editor:install:dry-run": "npm run dev:assets:generate && helm install code-editor helm-charts/code-editor --set dev=true -n code-editor --create-namespace --dry-run > out.yaml", + "dev:assets:generate": "npm run dev:users:generate && npm run dev:templates:generate && npm run cert:generate", + "dev:templates:generate": "sh scripts/generate-templates.sh 1", "dev:users:generate": "sh scripts/generate-users.sh 1", "traefik:install": "kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v2.10/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml -f https://raw.githubusercontent.com/traefik/traefik/v2.10/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml" }, diff --git a/scripts/generate-certificates.sh b/scripts/generate-certificates.sh index 9b49d57..ca62a23 100644 --- a/scripts/generate-certificates.sh +++ b/scripts/generate-certificates.sh @@ -1,5 +1,5 @@ #!/bin/sh -openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout charts/code-editor/tls.key -out charts/code-editor/tls.crt -subj "/C=XX/ST=Italy/L=Empoli/O=SUSE/OU=ECM/CN=code-editor" 2>/dev/null +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout helm-charts/code-editor/assets/tls.key -out helm-charts/code-editor/assets/tls.crt -subj "/C=XX/ST=Italy/L=Empoli/O=SUSE/OU=ECM/CN=code-editor" 2>/dev/null echo "Self-signed certificate successfully generated" \ No newline at end of file diff --git a/scripts/generate-certificates.ts b/scripts/generate-certificates.ts deleted file mode 100644 index 0029cd0..0000000 --- a/scripts/generate-certificates.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { exec } from 'child_process'; - -exec('openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout charts/code-editor/tls.key -out charts/code-editor/tls.crt -subj "/C=XX/ST=Italy/L=Empoli/O=SUSE/OU=ECM/CN=code-editor"', (error, stdout, stderr) => { - if (error) { - console.log(stderr); - } else { - console.log('Self-signed certificate successfully generated'); - } -}); \ No newline at end of file diff --git a/scripts/generate-templates.sh b/scripts/generate-templates.sh new file mode 100644 index 0000000..bff8a92 --- /dev/null +++ b/scripts/generate-templates.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +source_dir="src/templates" +helm_dir="helm-charts/code-editor/assets" +dev_dir="src/server/assets/templates" + +# Copy k8s templates to helm-charts +cp -a "$source_dir/." "$helm_dir/" + +# Add templates in dev-mode Go server +if [ ! -z $1 ] +then + echo 'dev mode' + + cp -a "$source_dir/." "$dev_dir/" +fi + +echo "Templates files successfully generated" diff --git a/scripts/generate-users.sh b/scripts/generate-users.sh index e75b4f1..0f13209 100644 --- a/scripts/generate-users.sh +++ b/scripts/generate-users.sh @@ -1,25 +1,17 @@ #!/bin/sh -dev_users="src/server/assets/users" +values="helm-charts/code-editor/values.yaml" +dev_dir="src/server/assets/users" -values="charts/code-editor/values.yaml" -users="charts/code-editor/users.yaml" - -rm -rf $users - -echo 'users:' >> $users -for k in $(yq '.users | to_entries | .[].key' $values) -do - echo " - name: $(yq ".users[$k].name" $values)" >> $users - echo " password: $(yq ".users[$k].password" $values)" >> $users - echo " path: $(tr -dc A-Za-z > $users -done - -# generate users file for Go +# Add users in dev-mode Go server if [ ! -z $1 ] then echo 'dev mode' - cat $users > $dev_users + + rm -rf $dev_dir + mkdir $dev_dir + echo "users: " > "$dev_dir/users.yaml" + yq '.users' $values >> "$dev_dir/users.yaml" fi echo "Users file successfully generated" diff --git a/src/go.work b/src/go.work index c8cd6e0..86faf8d 100644 --- a/src/go.work +++ b/src/go.work @@ -1,3 +1,3 @@ -go 1.20 +go 1.21.4 -use ./server \ No newline at end of file +use ./server diff --git a/src/go.work.sum b/src/go.work.sum index 754f757..b6ab4dd 100644 --- a/src/go.work.sum +++ b/src/go.work.sum @@ -1 +1,51 @@ -github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +k8s.io/apiserver v0.28.3/go.mod h1:YIpM+9wngNAv8Ctt0rHG4vQuX/I5rvkEMtZtsxW2rNM= +k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8= diff --git a/src/server/.gitignore b/src/server/.gitignore deleted file mode 100644 index 13cd1fa..0000000 --- a/src/server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -server \ No newline at end of file diff --git a/src/server/api/controllers.go b/src/server/api/controllers.go index 3c58219..fada191 100644 --- a/src/server/api/controllers.go +++ b/src/server/api/controllers.go @@ -5,9 +5,9 @@ import ( "net/http" "time" - authentication "server/authentication" - kube "server/kubernetes" - model "server/models" + "server/authentication" + "server/editor" + "server/models" "github.com/gin-gonic/gin" ) @@ -20,7 +20,7 @@ func Ping(c *gin.Context) { func Login(c *gin.Context) { - var auth model.Auth + var auth models.Auth if err := c.ShouldBindJSON(&auth); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) @@ -52,7 +52,9 @@ func (vw View) Enable(c *gin.Context) { user, _ := authentication.GetUser(c) - _, err := kube.ScaleCodeServer(user, 1) + editor := editor.New(user) + + port, password, err := editor.Create() if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Code-server - Cannot enable UI instance"}) return @@ -60,9 +62,9 @@ func (vw View) Enable(c *gin.Context) { time.Sleep(2000 * time.Millisecond) - session, err := authentication.EditorLogin(user) + session, err := editor.Login(port, password) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Code-server - Unauthorized"}) + c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) return } @@ -77,7 +79,9 @@ func (vw View) Disable(c *gin.Context) { user, _ := authentication.GetUser(c) - kube.ScaleCodeServer(user, 0) + editor := editor.New(user) + + editor.Destroy(user) c.JSON(http.StatusOK, gin.H{ "status": "disabled", }) @@ -87,7 +91,9 @@ func (vw View) Config(c *gin.Context) { user, _ := authentication.GetUser(c) - var vwConfig model.ViewConfig + editor := editor.New(user) + + var vwConfig models.ViewConfig if err := c.ShouldBindJSON(&vwConfig); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return @@ -104,17 +110,17 @@ func (vw View) Config(c *gin.Context) { git.Commit, ) - label := fmt.Sprintf("app.code-editor/path=%s", user.Path) - - err := kube.ExecCmdOnPod(label, gitCmd, nil, nil, nil) + err := editor.Config(gitCmd) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Pod Configuration failed; %s", err.Error())}) return } + queryParam := fmt.Sprintf("folder=/git/%s", git.Repo) + c.JSON(http.StatusOK, gin.H{ "status": "config saved", - "query-param": fmt.Sprintf("folder=/git/%s", git.Repo), + "query-param": queryParam, }) } diff --git a/src/server/assets/.gitignore b/src/server/assets/.gitignore index 5347237..4ef0ed4 100644 --- a/src/server/assets/.gitignore +++ b/src/server/assets/.gitignore @@ -1 +1,2 @@ +templates users \ No newline at end of file diff --git a/src/server/authentication/code-server.go b/src/server/authentication/code-server.go deleted file mode 100644 index a78a7f8..0000000 --- a/src/server/authentication/code-server.go +++ /dev/null @@ -1,71 +0,0 @@ -package authentication - -import ( - "errors" - "fmt" - "net/http" - "net/url" - "strconv" - "strings" - - "server/config" - "server/kubernetes" - "server/models" -) - -func EditorLogin(user models.User) (models.CodeServerSession, error) { - - var session models.CodeServerSession - - // code-server login endpoint - loginUrl := "" - - if config.Config.IsDev { - loginUrl = fmt.Sprintf("http://localhost/code-editor/%s/login", user.Path) - } else { - host := kubernetes.GetUserHost(user.Name) - port := kubernetes.GetUserPort(user.Name) - - loginUrl = fmt.Sprintf("http://%s:%s/login", host, port) - } - - // JSON body - data := url.Values{} - data.Set("password", user.Password) - - // Create a HTTP post request - client := &http.Client{ - CheckRedirect: func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - } - - req, err := http.NewRequest("POST", loginUrl, strings.NewReader(data.Encode())) - if err != nil { - return session, errors.New("Code-server, login request creation error") - } - - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode()))) - req.Header.Add("Host", "localhost") - - resp, err := client.Do(req) - - if err != nil { - return session, errors.New("Code-server, login response error") - } - defer resp.Body.Close() - - cookies := resp.Cookies() - - if len(cookies) == 0 { - return session, errors.New("Login failed, invalid User or Password") - } - - cookie := cookies[0] - - session.Name = cookie.Name - session.Value = cookie.Value - - return session, nil -} diff --git a/src/server/config/config.go b/src/server/config/config.go index 0ac414f..0880e67 100644 --- a/src/server/config/config.go +++ b/src/server/config/config.go @@ -22,19 +22,30 @@ func isDevEnv() bool { return false } -func getConfig() config { +func generatePath(users []models.User) { + for i := range users { + users[i].Path = utils.RandomString(13) + } +} + +func getUsers() map[string]models.User { + users := utils.ParseFile[models.Users]("assets/users/users.yaml").Users + + generatePath(users) + + return utils.Map(users, func(user models.User) string { return user.Name }) +} - arr := utils.ParseFile[models.Users]("assets/users").Users - userMap := utils.Map(arr, func(user models.User) string { return user.Name }) +func initConfig() config { c := config{ IsDev: isDevEnv(), App: "code-editor", Namespace: utils.IfNull(os.Getenv("POD_NAMESPACE"), "code-editor"), - Users: userMap, + Users: getUsers(), } return c } -var Config = getConfig() +var Config = initConfig() diff --git a/src/server/kubernetes/kubeconfig.go b/src/server/editor/kubeconfig.go similarity index 92% rename from src/server/kubernetes/kubeconfig.go rename to src/server/editor/kubeconfig.go index 7f7f3cf..2cb4657 100644 --- a/src/server/kubernetes/kubeconfig.go +++ b/src/server/editor/kubeconfig.go @@ -1,4 +1,4 @@ -package kubernetes +package editor import ( "flag" @@ -12,7 +12,7 @@ import ( "k8s.io/client-go/util/homedir" ) -func InitKubeconfig() (*kubernetes.Clientset, *rest.Config) { +func initKubeconfig() (*kubernetes.Clientset, *rest.Config) { localConfig := cfg.Config diff --git a/src/server/editor/manager.go b/src/server/editor/manager.go new file mode 100644 index 0000000..9d606d8 --- /dev/null +++ b/src/server/editor/manager.go @@ -0,0 +1,369 @@ +package editor + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "log" + "net/http" + "net/url" + "os" + "strconv" + "strings" + + config "server/config" + e "server/error" + "server/models" + "server/utils" + + corev1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/tools/remotecommand" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var clientset, restConfig = initKubeconfig() +var c = config.Config + +type logStreamer struct { + b bytes.Buffer +} + +func (l *logStreamer) String() string { + return l.b.String() +} + +func (l *logStreamer) Write(p []byte) (n int, err error) { + a := strings.TrimSpace(string(p)) + l.b.WriteString(a) + log.Printf(a) + return len(p), nil +} + +func execCmdOnPod(label string, command string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { + cmd := []string{ + "sh", + "-c", + command, + } + + pods, err := clientset.CoreV1().Pods(c.Namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: label}) + if err != nil || len(pods.Items) == 0 { + e.FailOnError(err, "Pod not found") + return err + } + + req := clientset.CoreV1().RESTClient().Post().Resource("pods").Name(pods.Items[0].Name).Namespace(c.Namespace).SubResource("exec") + + scheme := runtime.NewScheme() + if err := v1.AddToScheme(scheme); err != nil { + return err + } + + parameterCodec := runtime.NewParameterCodec(scheme) + req.VersionedParams(&v1.PodExecOptions{ + Stdin: true, + Stdout: true, + Stderr: false, + TTY: true, + Container: c.App, + Command: cmd, + }, parameterCodec) + url := req.URL() + exec, err := remotecommand.NewSPDYExecutor(restConfig, "POST", url) + if err != nil { + return err + } + var streamErr error + l := &logStreamer{} + + streamErr = exec.Stream(remotecommand.StreamOptions{ + Stdin: os.Stdin, + Stdout: l, + Stderr: nil, + Tty: true, + }) + + if streamErr != nil { + return streamErr + } + + return nil +} + +func waitPodRunning(ctx context.Context, label string) error { + watcher, err := clientset.CoreV1().Pods(c.Namespace).Watch(context.TODO(), metav1.ListOptions{ + LabelSelector: label, + }) + + if err != nil { + return err + } + + defer watcher.Stop() + + for { + select { + case event := <-watcher.ResultChan(): + pod := event.Object.(*v1.Pod) + + if pod.Status.Phase == v1.PodRunning { + return nil + } + case <-ctx.Done(): + return nil + } + } +} + +// func (k *k8sClient) waitPodDeleted(ctx context.Context, resName string) error { +// watcher, err := k.createPodWatcher(ctx, resName) +// if err != nil { +// return err +// } + +// defer watcher.Stop() + +// for { +// select { +// case event := <-watcher.ResultChan(): + +// if event.Type == watch.Deleted { +// k.logger.Debugf("The POD \"%s\" is deleted", resName) + +// return nil +// } + +// case <-ctx.Done(): +// k.logger.Debugf("Exit from waitPodDeleted for POD \"%s\" because the context is done", resName) +// return nil +// } +// } +// } + +func (editor Editor) Login(port int32, password string) (models.CodeServerSession, error) { + + var session models.CodeServerSession + + // code-server login endpoint + loginUrl := "" + + if config.Config.IsDev { + loginUrl = fmt.Sprintf("http://localhost/code-editor/%s/login", editor.path) + } else { + loginUrl = fmt.Sprintf("http://%s:%d/login", editor.id, port) + } + + // JSON body + data := url.Values{} + data.Set("password", password) + + // Create a HTTP post request + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + + req, err := http.NewRequest("POST", loginUrl, strings.NewReader(data.Encode())) + if err != nil { + return session, errors.New("Code-server, login request creation error") + } + + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode()))) + req.Header.Add("Host", "localhost") + + resp, err := client.Do(req) + + if err != nil { + return session, errors.New("Code-server, login response error") + } + defer resp.Body.Close() + + cookies := resp.Cookies() + + if len(cookies) == 0 { + return session, errors.New("Login failed, invalid User or Password") + } + + cookie := cookies[0] + + session.Name = cookie.Name + session.Value = cookie.Value + + return session, nil +} + +func deleteDeployment(user models.User, name string) error { + clientset.AppsV1().Deployments(c.Namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) + + return nil +} + +type EditorI interface { + Create() + Config() + Destroy() + Login() + credentialsCreate() + serviceCreate() + ruleCreate() + deploymentCreate() +} + +type Editor struct { + id string + namespace string + label string + path string +} + +func (editor Editor) credentialsCreate() *v1.Secret { + secret := utils.ParseK8sResource[*v1.Secret]("assets/templates/secret.yaml") + + secret.Name = editor.id + secret.Namespace = editor.namespace + + secret.StringData = map[string]string{ + "PASSWORD": utils.RandomString(20, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), + } + + clientset.CoreV1().Secrets(c.Namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) + + return secret +} + +func (editor Editor) serviceCreate() *v1.Service { + + service := utils.ParseK8sResource[*v1.Service]("assets/templates/service.yaml") + + service.Name = editor.id + service.Labels["app.kubernetes.io/name"] = editor.id + service.Spec.Selector["app.code-editor/path"] = editor.path + + clientset.CoreV1().Services(editor.namespace).Create(context.TODO(), service, metav1.CreateOptions{}) + + return service +} + +func (editor Editor) ruleCreate() error { + cli, _ := client.New(restConfig, client.Options{}) + + in := &unstructured.Unstructured{} + in.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "", + Kind: "IngressRoute", + Version: "traefik.containo.us/v1alpha1", + }) + + err := cli.Get(context.Background(), client.ObjectKey{ + Namespace: editor.namespace, + Name: "code-editor-ui", + }, in) + if err != nil { + e.FailOnError(err, "Failed to get Code-server IngressRoute") + } + + routeUnstructured := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "traefik.containo.us/v1alpha1", + "kind": "Rule", + "match": fmt.Sprintf("Host(`localhost`) && PathPrefix(`/code-editor/%s/`)", editor.path), + "middlewares": []interface{}{ + map[string]interface{}{ + "name": "strip-prefix", + }, + }, + "services": []interface{}{ + map[string]interface{}{ + "name": editor.id, + "port": "http", + }, + }, + }, + } + + routes, _, _ := unstructured.NestedSlice(in.Object, "spec", "routes") + + routes = append(routes, routeUnstructured.Object) + + if err := unstructured.SetNestedSlice(in.Object, routes, "spec", "routes"); err != nil { + e.FailOnError(err, "Failed to get Code-server IngressRoute") + } + + err = cli.Update(context.TODO(), in) + if err != nil { + e.FailOnError(err, "Failed to get Code-server IngressRoute") + } + + return nil +} + +func (editor Editor) deploymentCreate(service *v1.Service) *corev1.Deployment { + + deployment := utils.ParseK8sResource[*corev1.Deployment]("assets/templates/deployment.yaml") + + label := "app.code-editor/path" + + deployment.Name = editor.id + deployment.Labels[label] = editor.path + deployment.Spec.Selector.MatchLabels[label] = editor.path + deployment.Spec.Template.Labels[label] = editor.path + + deployment.Spec.Template.Spec.Containers[0].EnvFrom[0].SecretRef = &v1.SecretEnvSource{ + LocalObjectReference: v1.LocalObjectReference{ + Name: editor.id, + }, + } + + clientset.AppsV1().Deployments(c.Namespace).Create(context.TODO(), deployment, metav1.CreateOptions{}) + + return deployment +} + +func New(user models.User) Editor { + return Editor{ + id: fmt.Sprintf("%s-%s", c.App, user.Name), + namespace: c.Namespace, + label: fmt.Sprintf("app.code-editor/path=%s", user.Path), + path: user.Path, + } +} + +func (editor Editor) Create() (int32, string, error) { + + service := editor.serviceCreate() + + editor.ruleCreate() + + secret := editor.credentialsCreate() + + editor.deploymentCreate(service) + + waitPodRunning(context.TODO(), editor.label) + + port := service.Spec.Ports[0].Port + password := secret.StringData["PASSWORD"] + + return port, password, nil +} + +func (editor Editor) Config(gitCmd string) error { + execCmdOnPod(editor.label, gitCmd, nil, nil, nil) + + return nil +} + +func (editor Editor) Destroy(user models.User) error { + name := fmt.Sprintf("%s-%s", c.App, user.Name) + + return deleteDeployment(user, name) +} diff --git a/src/server/go.mod b/src/server/go.mod index 8a9888d..125abcb 100644 --- a/src/server/go.mod +++ b/src/server/go.mod @@ -1,26 +1,28 @@ module server -go 1.20 +go 1.21.4 require ( + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/fatih/structs v1.1.0 github.com/gin-gonic/gin v1.9.1 + golang.org/x/crypto v0.17.0 gopkg.in/yaml.v2 v2.4.0 - k8s.io/apimachinery v0.28.4 - k8s.io/client-go v0.28.4 + k8s.io/api v0.29.0 + k8s.io/apimachinery v0.29.0 + k8s.io/client-go v0.29.0 + sigs.k8s.io/controller-runtime v0.16.3 ) -require github.com/moby/spdystream v0.2.0 // indirect - require ( github.com/bytedance/sonic v1.9.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dgrijalva/jwt-go v3.2.0+incompatible - github.com/emicklei/go-restful/v3 v3.9.0 // indirect - github.com/fatih/structs v1.1.0 + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect @@ -31,9 +33,9 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -41,30 +43,31 @@ require ( github.com/leodido/go-urn v1.2.4 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.19 // indirect + github.com/moby/spdystream v0.2.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.14.0 golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/oauth2 v0.10.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.28.4 - k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect - k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/src/server/go.sum b/src/server/go.sum index 1faa6dc..09eef15 100644 --- a/src/server/go.sum +++ b/src/server/go.sum @@ -1,7 +1,12 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= @@ -11,19 +16,24 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= +github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= @@ -31,6 +41,7 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= @@ -38,6 +49,7 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -49,17 +61,22 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -71,6 +88,7 @@ github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZX github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -81,6 +99,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -90,13 +110,29 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -108,22 +144,29 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -133,8 +176,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= +golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -143,26 +186,29 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= +golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= @@ -180,22 +226,26 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= -k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= -k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= -k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= -k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= -k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= +k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= +k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= +k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= +k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= +k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= +k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= +k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= +sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/src/server/kubernetes/manager.go b/src/server/kubernetes/manager.go deleted file mode 100644 index 4b72606..0000000 --- a/src/server/kubernetes/manager.go +++ /dev/null @@ -1,176 +0,0 @@ -package kubernetes - -import ( - "bytes" - "context" - "fmt" - "io" - "log" - "os" - "strings" - - config "server/config" - e "server/error" - "server/models" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/tools/remotecommand" -) - -var clientset, restConfig = InitKubeconfig() -var c = config.Config - -type LogStreamer struct { - b bytes.Buffer -} - -func (l *LogStreamer) String() string { - return l.b.String() -} - -func (l *LogStreamer) Write(p []byte) (n int, err error) { - a := strings.TrimSpace(string(p)) - l.b.WriteString(a) - log.Printf(a) - return len(p), nil -} - -func ExecCmdOnPod(label string, command string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { - cmd := []string{ - "sh", - "-c", - command, - } - - pods, err := clientset.CoreV1().Pods(c.Namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: label}) - if err != nil || len(pods.Items) == 0 { - e.FailOnError(err, "Pod not found") - return err - } - - req := clientset.CoreV1().RESTClient().Post().Resource("pods").Name(pods.Items[0].Name).Namespace(c.Namespace).SubResource("exec") - - scheme := runtime.NewScheme() - if err := v1.AddToScheme(scheme); err != nil { - return err - } - - parameterCodec := runtime.NewParameterCodec(scheme) - req.VersionedParams(&v1.PodExecOptions{ - Stdin: true, - Stdout: true, - Stderr: false, - TTY: true, - Container: c.App, - Command: cmd, - }, parameterCodec) - url := req.URL() - exec, err := remotecommand.NewSPDYExecutor(restConfig, "POST", url) - if err != nil { - return err - } - var streamErr error - l := &LogStreamer{} - - streamErr = exec.Stream(remotecommand.StreamOptions{ - Stdin: os.Stdin, - Stdout: l, - Stderr: nil, - Tty: true, - }) - - if streamErr != nil { - return streamErr - } - - return nil -} - -func waitPodRunning(ctx context.Context, label string) error { - watcher, err := clientset.CoreV1().Pods(c.Namespace).Watch(context.TODO(), metav1.ListOptions{ - LabelSelector: label, - }) - - if err != nil { - return err - } - - defer watcher.Stop() - - for { - select { - case event := <-watcher.ResultChan(): - pod := event.Object.(*v1.Pod) - - if pod.Status.Phase == v1.PodRunning { - return nil - } - case <-ctx.Done(): - return nil - } - } -} - -// func (k *k8sClient) waitPodDeleted(ctx context.Context, resName string) error { -// watcher, err := k.createPodWatcher(ctx, resName) -// if err != nil { -// return err -// } - -// defer watcher.Stop() - -// for { -// select { -// case event := <-watcher.ResultChan(): - -// if event.Type == watch.Deleted { -// k.logger.Debugf("The POD \"%s\" is deleted", resName) - -// return nil -// } - -// case <-ctx.Done(): -// k.logger.Debugf("Exit from waitPodDeleted for POD \"%s\" because the context is done", resName) -// return nil -// } -// } -// } - -func ScaleCodeServer(user models.User, replicas int) (string, error) { - - num := int32(replicas) - namespace := c.Namespace - name := fmt.Sprintf("%s-%s", c.App, user.Name) - - deployment := clientset.AppsV1().Deployments(namespace) - - s, err := deployment.GetScale(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - e.FailOnError(err, "Failed to get Code-server Deployment/Scale") - } - - sc := *s - - if sc.Spec.Replicas == num { - log.Printf("Deployment is already in desired status") - return "", nil - } - - sc.Spec.Replicas = num - - _, err = deployment.UpdateScale(context.TODO(), name, &sc, metav1.UpdateOptions{}) - - if err != nil { - return "", err - } - - label := fmt.Sprintf("app.code-editor/path=%s", user.Path) - - if num > 0 { - waitPodRunning(context.TODO(), label) - } - - return "", nil -} diff --git a/src/server/kubernetes/routes.go b/src/server/kubernetes/routes.go deleted file mode 100644 index 969d1bc..0000000 --- a/src/server/kubernetes/routes.go +++ /dev/null @@ -1,15 +0,0 @@ -package kubernetes - -import ( - "fmt" - "os" - "strings" -) - -func GetUserHost(user string) string { - return os.Getenv(fmt.Sprintf("CODE_EDITOR_%s_SERVICE_HOST", strings.ToUpper(user))) -} - -func GetUserPort(user string) string { - return os.Getenv(fmt.Sprintf("CODE_EDITOR_%s_SERVICE_PORT", strings.ToUpper(user))) -} diff --git a/src/server/models/models.go b/src/server/models/models.go index 30b5b4c..03f3641 100644 --- a/src/server/models/models.go +++ b/src/server/models/models.go @@ -15,6 +15,16 @@ type Users struct { Users []User } +type Service struct { + Id string `yaml:"id"` + Port int32 `yaml:"port"` +} + +type CodeServerConfig struct { + ServicePort int32 `yaml:"port"` + Password string `yaml:"password"` +} + type CodeServerSession struct { Name string `yaml:"name"` Value string `yaml:"value"` diff --git a/src/server/utils/string.go b/src/server/utils/string.go index 64e8c5e..10ceb66 100644 --- a/src/server/utils/string.go +++ b/src/server/utils/string.go @@ -4,18 +4,33 @@ import ( "encoding/json" "fmt" "math/rand" + "strings" ) func randInt(min int, max int) int { return min + rand.Intn(max-min) } -func RandomString(l int) string { - bytes := make([]byte, l) - for i := 0; i < l; i++ { - bytes[i] = byte(randInt(65, 90)) +func RandomString(n int, r ...string) string { + + var alphabet []rune + + if len(r) > 0 { + alphabet = []rune(r[0]) + } else { + alphabet = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") + } + + alphabetSize := len(alphabet) + var sb strings.Builder + + for i := 0; i < n; i++ { + ch := alphabet[rand.Intn(alphabetSize)] + sb.WriteRune(ch) } - return string(bytes) + + s := sb.String() + return s } func ToString(v any) string { diff --git a/src/server/utils/yaml.go b/src/server/utils/yaml.go index 1aae96d..cbcc838 100644 --- a/src/server/utils/yaml.go +++ b/src/server/utils/yaml.go @@ -4,6 +4,8 @@ import ( "log" "os" + "k8s.io/client-go/kubernetes/scheme" + "gopkg.in/yaml.v2" ) @@ -21,3 +23,19 @@ func ParseFile[T any](path string) T { return res } + +func ParseK8sResource[T any](path string) T { + decode := scheme.Codecs.UniversalDeserializer().Decode + + stream, err := os.ReadFile(path) + if err != nil { + log.Fatalf("error: %v", err) + } + + obj, _, err := decode(stream, nil, nil) + if err != nil { + log.Fatalf("error: %v", err) + } + + return obj.(T) +} diff --git a/src/templates/deployment.yaml b/src/templates/deployment.yaml new file mode 100644 index 0000000..8337f5a --- /dev/null +++ b/src/templates/deployment.yaml @@ -0,0 +1,145 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + meta.helm.sh/release-name: code-editor + meta.helm.sh/release-namespace: code-editor + labels: + app.code-editor/path: "" # user path + app.kubernetes.io/instance: code-editor + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: code-editor + helm.sh/chart: code-editor-1.0.0 + name: "" # unique for user + namespace: code-editor +spec: + replicas: 1 + selector: + matchLabels: + app.code-editor/path: "" # user path + app.kubernetes.io/instance: code-editor + app.kubernetes.io/name: code-editor + strategy: + type: Recreate + template: + metadata: + labels: + app.code-editor/path: "" # user path + app.kubernetes.io/instance: code-editor + app.kubernetes.io/name: code-editor + spec: + containers: + - args: + - --disable-telemetry + - --disable-getting-started-override + - --disable-workspace-trust + image: codercom/code-server:4.19.0 + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 5 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: 8080 + timeoutSeconds: 1 + name: code-editor + ports: + - containerPort: 8080 + name: http + protocol: TCP + readinessProbe: + failureThreshold: 5 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: 8080 + timeoutSeconds: 1 + resources: {} + securityContext: + runAsUser: 1000 + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /home/coder/.local/share/code-server/User + name: vscode-config + - mountPath: /home/coder/.ssh + name: ssh-root + - mountPath: /git + name: data + - mountPath: /home/coder + name: cfg + envFrom: + - secretRef: + name: "" # code-server password + dnsPolicy: ClusterFirst + initContainers: + - command: + - /bin/sh + - -c + - cp /ssh/id_ed25519 /etc/ssh-key/id_ed25519 && ssh-keyscan -t ed25519 github.com >> /etc/ssh-key/known_hosts && chown -R 1000:1000 /etc/ssh-key && chmod 700 /etc/ssh-key/id_ed25519 + image: kroniak/ssh-client:latest + imagePullPolicy: Always + name: ssh + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /ssh + name: ssh + - mountPath: /etc/ssh-key + name: ssh-root + - command: + - /bin/sh + - -c + - "chown -R 1000:1000 /etc && echo '[user]\n\temail = francesco.torchia@suse.com\n\tname + = Francesco Torchia' > /home/coder/.gitconfig" + image: busybox + imagePullPolicy: Always + name: gitconfig + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /home/coder/ + name: cfg + - command: + - sh + - -c + - | + code-server --install-extension hoovercj.vscode-power-mode + env: + - name: SERVICE_URL + value: https://open-vsx.org/vscode/gallery + - name: ITEM_URL + value: https://open-vsx.org/vscode/item + image: codercom/code-server:4.19.0 + imagePullPolicy: IfNotPresent + name: customization + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /home/coder + name: cfg + restartPolicy: Always + schedulerName: default-scheduler + securityContext: + fsGroup: 1000 + serviceAccount: code-editor + serviceAccountName: code-editor + terminationGracePeriodSeconds: 30 + volumes: + - configMap: + defaultMode: 420 + name: vscode-config + name: vscode-config + - name: ssh + secret: + defaultMode: 420 + secretName: secret-ssh-auth + - emptyDir: {} + name: ssh-root + - emptyDir: {} + name: cfg + - emptyDir: {} + name: data diff --git a/src/templates/secret.yaml b/src/templates/secret.yaml new file mode 100644 index 0000000..98c426f --- /dev/null +++ b/src/templates/secret.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: "" # unique for user + labels: + app.kubernetes.io/name: code-editor +type: Opaque diff --git a/src/templates/service.yaml b/src/templates/service.yaml new file mode 100644 index 0000000..4c12518 --- /dev/null +++ b/src/templates/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: "" # unique for user + labels: + app.kubernetes.io/name: "" # unique for user + helm.sh/chart: "" # unique for user + app.kubernetes.io/instance: code-editor + app.kubernetes.io/managed-by: Helm +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: http + protocol: TCP + name: http + selector: + app.code-editor/path: "" # user path \ No newline at end of file diff --git a/src/templates/traefik-route.yaml b/src/templates/traefik-route.yaml new file mode 100644 index 0000000..550ee46 --- /dev/null +++ b/src/templates/traefik-route.yaml @@ -0,0 +1,7 @@ +kind: Rule +match: "" # match user path +middlewares: + - name: strip-prefix +services: + - name: "" # unique for user + port: http diff --git a/charts/code-editor/vscode-settings.json b/src/templates/vscode-settings.json similarity index 100% rename from charts/code-editor/vscode-settings.json rename to src/templates/vscode-settings.json diff --git a/todo.md b/todo.md index 619592b..e5a5817 100644 --- a/todo.md +++ b/todo.md @@ -1,9 +1,15 @@ Todo +- server refactoring, example https://github.com/golang-standards/project-layout +- errors handling +- add user ID +- make "code-editor" prefix parametric +- change api response, status should be in a higher level than data +- Replace username with user ID in routing and object names +- vs-code-settings and extensions must be installed via APis, not hardcoded -> remove them from src/templates - Write license -- Replace username with paths in routing and object names - Create an external authentication mode: - -1 pre-requisite: routes dynamic creation (services, deployments, ingressroutes) + -1 DONE pre-requisite: routes dynamic creation (services, deployments, ingressroutes) -2 helm param to switch to external authentication -3 helm param to save the url of external login -4 implement a new /register-user endpoint to make the login to the external authentication and register the user into code-editor @@ -21,3 +27,4 @@ Todo - Implement a k8s controller to create a pool of code-server pods to be assigned dynamically to the users, with dynamic authentication - Implement a k8s controller to keep healthy code-server pods - Refactoring docs + readme example: https://github.com/andreabenini/podmaster/tree/main/forklift/