diff --git a/internal/config/types.go b/internal/config/types.go index 227897d..aed7693 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -28,6 +28,10 @@ type BaseConfig struct { ClearLocalPackages bool `yaml:"clearLocalPackages,omitempty"` ClearVSCodeCache bool `yaml:"clearVSCodeCache,omitempty"` PythonBinPath string `yaml:"pythonBinPath,omitempty" validate:"omitempty,min=1,filepath"` + HostName string `yaml:"hostName,omitempty" validate:"omitempty,min=1,hostname"` + EnableAuth bool `yaml:"enableAuth,omitempty"` + AuthURL string `yaml:"authURL,omitempty" validate:"omitempty,min=1,url"` + AuthSignIn string `yaml:"authSignIn,omitempty" validate:"omitempty,min=1,url"` } // DevEnvConfig represents the complete configuration for a developer environment. diff --git a/internal/templates/files/env-vars.tmpl b/internal/templates/files/env-vars.tmpl index cda52d0..1597a53 100644 --- a/internal/templates/files/env-vars.tmpl +++ b/internal/templates/files/env-vars.tmpl @@ -7,5 +7,6 @@ metadata: data: USER: "{{.Name}}" UID: "{{.GetUserID}}" + IS_ADMIN: "{{.IsAdmin}}" GIT_NAME: "{{.Git.Name}}" GIT_EMAIL: "{{.Git.Email}}" diff --git a/internal/templates/files/ingress.tmpl b/internal/templates/files/ingress.tmpl new file mode 100644 index 0000000..5f70fe1 --- /dev/null +++ b/internal/templates/files/ingress.tmpl @@ -0,0 +1,31 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: devenv-ingress-{{.Name}} + annotations: + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + cert-manager.io/cluster-issuer: "letsencrypt" + + {{- if and .EnableAuth }} + nginx.ingress.kubernetes.io/auth-url: "{{.AuthURL}}" + nginx.ingress.kubernetes.io/auth-signin: "{{.AuthSignIn}}?rd=$scheme://$host$escaped_request_uri" + nginx.ingress.kubernetes.io/auth-response-headers: "Authorization,X-Auth-Request-User,X-Auth-Request-Email,X-Auth-Request-Access-Token" + {{- end}} + +spec: + ingressClassName: nginx + rules: + - host: {{.Name}}.{{.HostName}} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: devenv-http-{{.Name}} + port: + name: http + tls: + - hosts: + - "*.{{.HostName}}" + secretName: http-{{.Name}}-tls \ No newline at end of file diff --git a/internal/templates/files/startup-scripts.tmpl b/internal/templates/files/startup-scripts.tmpl index f5f47f1..9f39413 100644 --- a/internal/templates/files/startup-scripts.tmpl +++ b/internal/templates/files/startup-scripts.tmpl @@ -7,16 +7,16 @@ metadata: data: # Templated script - processed with config values startup.sh: | -{{getTemplatedScript "startup.sh" . | indent 4}} + {{getTemplatedScript "startup.sh" . | indent 4}} # Static utility scripts - included as-is run_with_git.sh: | -{{getStaticScript "run_with_git.sh" | indent 4}} + {{getStaticScript "run_with_git.sh" | indent 4}} # Static requirements file requirements.txt: | -{{getStaticScript "requirements.txt" | indent 4}} + {{getStaticScript "requirements.txt" | indent 4}} # User setup script setup.sh: | -{{getTemplatedScript "user-setup.sh" . | indent 4}} \ No newline at end of file + {{getTemplatedScript "user-setup.sh" . | indent 4}} \ No newline at end of file diff --git a/internal/templates/files/statefulset.tmpl b/internal/templates/files/statefulset.tmpl index 3e86f65..89fdfce 100644 --- a/internal/templates/files/statefulset.tmpl +++ b/internal/templates/files/statefulset.tmpl @@ -42,15 +42,38 @@ spec: containers: - name: {{.Name}} image: {{.Image}} + workingDir: "/src" + securityContext: + # Root required to configure new user and setup sshd + runAsUser: 0 + command: ["/bin/bash", "/scripts/startup.sh"] ports: - containerPort: 22 name: ssh - protocol: TCP {{- if ne .HTTPPort 0}} - containerPort: {{.HTTPPort}} name: http - protocol: TCP {{- end}} + + readinessProbe: + tcpSocket: + port: 22 + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 6 + + env: + - name: GITHUB_TOKEN + valueFrom: + secretKeyRef: + name: github-token + key: token + optional: true + envFrom: + - configMapRef: + name: env-vars-{{.Name}} + resources: limits: {{- if gt (.GPU) 0}} @@ -72,20 +95,6 @@ spec: {{- if ne (.MemoryRequest) "unlimited"}} memory: "{{.MemoryRequest}}" {{- end}} - env: - - name: USERNAME - value: {{.Name}} - - name: USER_ID - value: "{{if .UID}}{{.UID}}{{else}}1000{{end}}" - - name: IS_ADMIN - value: "{{.IsAdmin}}" - envFrom: - - configMapRef: - name: env-vars-{{.Name}} - - securityContext: - runAsUser: {{if .UID}}{{.UID}}{{else}}1000{{end}} - runAsGroup: {{if .UID}}{{.UID}}{{else}}1000{{end}} volumeMounts: - name: dev-storage @@ -95,9 +104,6 @@ spec: - name: startup-scripts mountPath: /scripts readOnly: true - - name: ssh-keys - mountPath: /home/{{.Name}}/.ssh - readOnly: true {{- range .Volumes}} - name: {{.Name}} mountPath: {{.ContainerPath}} @@ -116,13 +122,9 @@ spec: configMap: name: startup-scripts-{{.Name}} defaultMode: 0755 - - name: ssh-keys - secret: - secretName: ssh-keys-{{.Name}} - defaultMode: 0600 {{- range .Volumes}} - name: {{.Name}} hostPath: path: {{.LocalPath}} - type: Directory + type: DirectoryOrCreate {{- end}} \ No newline at end of file diff --git a/internal/templates/renderer.go b/internal/templates/renderer.go index 3fcc75d..1aac66a 100644 --- a/internal/templates/renderer.go +++ b/internal/templates/renderer.go @@ -12,6 +12,9 @@ import ( "github.com/nauticalab/devenv-engine/internal/config" ) +var templatesToRender = []string{"statefulset", "service", "env-vars", + "secret", "startup-scripts", "ingress"} + // Embed all templates and scripts at compile time // //go:embed files/*.tmpl @@ -113,7 +116,6 @@ func (r *Renderer) RenderTemplate(templateName string, config *config.DevEnvConf } func (r *Renderer) RenderAll(config *config.DevEnvConfig) error { - templatesToRender := []string{"statefulset", "service", "env-vars", "secret", "startup-scripts"} for _, templateName := range templatesToRender { if err := r.RenderTemplate(templateName, config); err != nil { return fmt.Errorf("failed to render template %s: %w", templateName, err) diff --git a/internal/templates/testdata/golden/env-vars.yaml b/internal/templates/testdata/golden/env-vars.yaml index 9cad88c..1d9015f 100644 --- a/internal/templates/testdata/golden/env-vars.yaml +++ b/internal/templates/testdata/golden/env-vars.yaml @@ -7,5 +7,6 @@ metadata: data: USER: "testuser" UID: "2000" + IS_ADMIN: "true" GIT_NAME: "Test User" GIT_EMAIL: "testuser@example.com" diff --git a/internal/templates/testdata/golden/startup-scripts.yaml b/internal/templates/testdata/golden/startup-scripts.yaml index fd8370f..b55ae86 100644 --- a/internal/templates/testdata/golden/startup-scripts.yaml +++ b/internal/templates/testdata/golden/startup-scripts.yaml @@ -7,7 +7,7 @@ metadata: data: # Templated script - processed with config values startup.sh: | -#!/bin/bash + #!/bin/bash # Container startup script for developer environment: testuser set -e @@ -157,7 +157,7 @@ data: # Static utility scripts - included as-is run_with_git.sh: | -#!/bin/bash + #!/bin/bash # # run_with_git - Run commands as another user with temporary Git credentials # @@ -217,7 +217,7 @@ data: # Static requirements file requirements.txt: | -# Common Python packages for scientific computing + # Common Python packages for scientific computing # This file contains baseline packages that are installed in all environments # Core scientific stack @@ -246,7 +246,7 @@ data: # User setup script setup.sh: | -#!/bin/bash + #!/bin/bash # User environment setup script for: testuser # This script runs as the developer user to configure their personal environment set -e diff --git a/internal/templates/testdata/golden/statefulset.yaml b/internal/templates/testdata/golden/statefulset.yaml index 0fa79f3..e8b0a52 100644 --- a/internal/templates/testdata/golden/statefulset.yaml +++ b/internal/templates/testdata/golden/statefulset.yaml @@ -33,13 +33,36 @@ spec: containers: - name: testuser image: ubuntu:22.04 + workingDir: "/src" + securityContext: + # Root required to configure new user and setup sshd + runAsUser: 0 + command: ["/bin/bash", "/scripts/startup.sh"] ports: - containerPort: 22 name: ssh - protocol: TCP - containerPort: 8080 name: http - protocol: TCP + + readinessProbe: + tcpSocket: + port: 22 + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 6 + + env: + - name: GITHUB_TOKEN + valueFrom: + secretKeyRef: + name: github-token + key: token + optional: true + envFrom: + - configMapRef: + name: env-vars-testuser + resources: limits: nvidia.com/gpu: 2 @@ -49,20 +72,6 @@ spec: nvidia.com/gpu: 2 cpu: "4" memory: "16Gi" - env: - - name: USERNAME - value: testuser - - name: USER_ID - value: "2000" - - name: IS_ADMIN - value: "true" - envFrom: - - configMapRef: - name: env-vars-testuser - - securityContext: - runAsUser: 2000 - runAsGroup: 2000 volumeMounts: - name: dev-storage @@ -72,9 +81,6 @@ spec: - name: startup-scripts mountPath: /scripts readOnly: true - - name: ssh-keys - mountPath: /home/testuser/.ssh - readOnly: true - name: data-volume mountPath: /data - name: config-volume @@ -93,15 +99,11 @@ spec: configMap: name: startup-scripts-testuser defaultMode: 0755 - - name: ssh-keys - secret: - secretName: ssh-keys-testuser - defaultMode: 0600 - name: data-volume hostPath: path: /mnt/data - type: Directory + type: DirectoryOrCreate - name: config-volume hostPath: path: /mnt/config - type: Directory \ No newline at end of file + type: DirectoryOrCreate \ No newline at end of file