├── .github ├── CODEOWNERS ├── dependabot.yml ├── stale.yml └── workflows │ └── transformer-images.yaml ├── go.mod ├── templates ├── base │ ├── _index.tmpl │ ├── namespace.tmpl │ ├── confimap.tmpl │ ├── secret.tmpl │ ├── expose.tmpl │ ├── networkpolicy.tmpl │ ├── persistentVolumeClaim.tmpl │ ├── service.tmpl │ └── deployment.tmpl └── overlays │ ├── model-runner │ ├── _index.tmpl │ ├── model-runner-configmap.tmpl │ ├── model-runner-volume-claim.tmpl │ ├── model-runner-service.tmpl │ └── model-runner-deployment.tmpl │ └── desktop │ ├── _index.tmpl │ ├── persistentVolumeClaim.tmpl │ ├── service.tmpl │ └── deployment.tmpl ├── helm-templates ├── templates │ ├── namespace.tmpl │ ├── secret.tmpl │ ├── confimap.tmpl │ ├── model-runner-service.tmpl │ ├── model-runner-pvc.tmpl │ ├── networkpolicy.tmpl │ ├── expose.tmpl │ ├── persistentVolumeClaim.tmpl │ ├── service.tmpl │ ├── model-runner-deployment.tmpl │ └── deployment.tmpl ├── Chart.tmpl └── values.tmpl ├── go.sum ├── Dockerfile ├── docker-bake.hcl ├── README.md ├── convert.go └── LICENSE /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @docker/compose-team 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/docker/kouign-amann 2 | 3 | go 1.24.5 4 | 5 | require go.yaml.in/yaml/v3 v3.0.4 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: / 5 | schedule: 6 | interval: daily 7 | -------------------------------------------------------------------------------- /templates/base/_index.tmpl: -------------------------------------------------------------------------------- 1 | #! kustomization.yaml 2 | # Generated code, do not edit 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | 6 | resources: 7 | {{ range . }} 8 | {{ if ne .Name "kustomization.yaml" }} 9 | - {{ .Name }} 10 | {{ end }} 11 | {{ end }} 12 | -------------------------------------------------------------------------------- /templates/base/namespace.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | --- 3 | #! 0-{{ $project | safe }}-namespace.yaml 4 | # Generated code, do not edit 5 | apiVersion: v1 6 | kind: Namespace 7 | metadata: 8 | name: {{ $project | safe }} 9 | labels: 10 | com.docker.compose.project: {{ $project }} 11 | -------------------------------------------------------------------------------- /helm-templates/templates/namespace.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | --- 3 | #! 0-{{ $project | safe }}-namespace.yaml 4 | # Generated code, do not edit 5 | apiVersion: v1 6 | kind: Namespace 7 | metadata: 8 | name: {{ helmValue ".Values.namespace" }} 9 | labels: 10 | com.docker.compose.project: {{ $project }} 11 | -------------------------------------------------------------------------------- /templates/overlays/model-runner/_index.tmpl: -------------------------------------------------------------------------------- 1 | {{ if . }} 2 | #! kustomization.yaml 3 | # Generated code, do not edit 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | 7 | resources: 8 | - ../../base 9 | {{ range . }} 10 | {{ if ne .Name "kustomization.yaml" }} 11 | - {{ .Name }} 12 | {{ end }} 13 | {{ end }} 14 | {{ end }} 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= 2 | go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 3 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 4 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 5 | -------------------------------------------------------------------------------- /helm-templates/Chart.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | #! Chart.yaml 3 | apiVersion: v2 4 | name: {{ $project | safe }} 5 | version: 0.0.1 6 | # kubeVersion: >= 1.29.1 7 | description: A generated Helm Chart for {{ $project }} generated via compose-bridge. 8 | type: application 9 | keywords: 10 | - {{ $project | safe }} 11 | appVersion: 'v0.0.1' 12 | sources: 13 | annotations: -------------------------------------------------------------------------------- /templates/overlays/desktop/_index.tmpl: -------------------------------------------------------------------------------- 1 | #! kustomization.yaml 2 | # Generated code, do not edit 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | 6 | resources: 7 | - ../../base 8 | 9 | {{ if or (gt (len .) 1) (and (eq (len .) 1) (ne (index . 0).Name "kustomization.yaml")) }} 10 | patches: 11 | {{ range . }} 12 | {{ if ne .Name "kustomization.yaml" }} 13 | - path: {{ .Name }} 14 | {{ end }} 15 | {{ end }} 16 | {{ end }} -------------------------------------------------------------------------------- /templates/base/confimap.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ if .configs }} 3 | --- 4 | #! {{ $project }}-configs.yaml 5 | # Generated code, do not edit 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: {{ $project | safe }} 10 | namespace: {{ $project | safe }} 11 | labels: 12 | com.docker.compose.project: {{ $project }} 13 | data: 14 | {{ range $name, $config := .configs }} 15 | {{ $name | safe }}: | 16 | {{ indent $config.content 4 }} 17 | {{ end }} 18 | {{ end }} -------------------------------------------------------------------------------- /templates/base/secret.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ range $name, $secret := .secrets }} 3 | --- 4 | #! {{ $name }}-secret.yaml 5 | # Generated code, do not edit 6 | apiVersion: v1 7 | kind: Secret 8 | metadata: 9 | name: {{ $name | safe }} 10 | namespace: {{ $project | safe }} 11 | labels: 12 | com.docker.compose.project: {{ $project }} 13 | com.docker.compose.secret: {{ $name }} 14 | data: 15 | {{ $name }}: {{ $secret.content | base64 }} 16 | type: Opaque 17 | {{ end }} 18 | -------------------------------------------------------------------------------- /helm-templates/templates/secret.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ range $name, $secret := .secrets }} 3 | --- 4 | #! {{ $name }}-secret.yaml 5 | # Generated code, do not edit 6 | apiVersion: v1 7 | kind: Secret 8 | metadata: 9 | name: {{ $name | safe }} 10 | namespace: {{ helmValue ".Values.namespace" }} 11 | labels: 12 | com.docker.compose.project: {{ $project }} 13 | com.docker.compose.secret: {{ $name }} 14 | data: 15 | {{ $name }}: {{ $secret.content | base64 }} 16 | type: Opaque 17 | {{ end }} 18 | -------------------------------------------------------------------------------- /helm-templates/templates/confimap.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ if .configs }} 3 | --- 4 | #! {{ $project }}-configs.yaml 5 | # Generated code, do not edit 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: {{ helmValue ".Values.projectName"}} 10 | namespace: {{ helmValue ".Values.namespace" }} 11 | labels: 12 | com.docker.compose.project: {{ $project }} 13 | data: 14 | {{ range $name, $config := .configs }} 15 | {{ $name | safe }}: | 16 | {{ indent $config.content 4 }} 17 | {{ end }} 18 | {{ end }} -------------------------------------------------------------------------------- /templates/overlays/model-runner/model-runner-configmap.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ if .models }} 3 | --- 4 | #! model-runner-configmap.yaml 5 | # Generated code, do not edit 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: docker-model-runner-init 10 | namespace: {{ $project | safe }} 11 | labels: 12 | com.docker.compose.project: {{ $project }} 13 | com.docker.model.runner: "true" 14 | data: 15 | models: |{{ range $name, $model := .models }} 16 | {{ indent $model.model 4 }} 17 | {{ end }} 18 | {{ end }} -------------------------------------------------------------------------------- /templates/overlays/desktop/persistentVolumeClaim.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ range $name, $service := .services }} 3 | {{ range $volume := $service.volumes }} 4 | --- 5 | #! {{ $name }}-{{ $volume.source }}-persistentVolumeClaim.yaml 6 | # Generated code, do not edit 7 | apiVersion: v1 8 | kind: PersistentVolumeClaim 9 | metadata: 10 | name: {{ $name | safe }}-{{ $volume.source | safe }} 11 | namespace: {{ $project | safe }} 12 | spec: 13 | storageClassName: "hostpath" # see docker/desktop-storage-provisioner 14 | {{ end }} 15 | {{ end }} -------------------------------------------------------------------------------- /templates/overlays/model-runner/model-runner-volume-claim.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ if .models }} 3 | --- 4 | #! model-runner-volume-claim.yaml 5 | # Generated code, do not edit 6 | apiVersion: v1 7 | kind: PersistentVolumeClaim 8 | metadata: 9 | name: model-storage 10 | namespace: {{ $project | safe }} 11 | labels: 12 | com.docker.compose.project: {{ $project }} 13 | com.docker.model.runner: "true" 14 | spec: 15 | accessModes: 16 | - ReadWriteOnce 17 | volumeMode: Filesystem 18 | resources: 19 | requests: 20 | storage: 100Gi 21 | {{ end }} -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=${BUILDPLATFORM} golang:1.24.5 AS builder 2 | WORKDIR $GOPATH/src/github.com/docker/compose-bridge-transformer 3 | COPY . . 4 | RUN go build -o /go/bin/transform 5 | 6 | FROM scratch AS transformer 7 | LABEL com.docker.compose.bridge=transformation 8 | COPY --from=builder /go/bin/transform /transform 9 | CMD ["/transform"] 10 | 11 | FROM transformer AS kubernetes 12 | LABEL com.docker.compose.bridge=transformation 13 | COPY templates /templates 14 | 15 | FROM transformer AS helm 16 | LABEL com.docker.compose.bridge=transformation 17 | COPY helm-templates /templates -------------------------------------------------------------------------------- /helm-templates/templates/model-runner-service.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ if .models }} 3 | --- 4 | #! model-runner-service.yaml 5 | apiVersion: v1 6 | kind: Service 7 | metadata: 8 | name: docker-model-runner 9 | namespace: {{ helmValue ".Values.namespace" }} 10 | labels: 11 | app: docker-model-runner 12 | com.docker.compose.project: {{ $project }} 13 | app.kubernetes.io/managed-by: Helm 14 | spec: 15 | type: ClusterIP 16 | ports: 17 | - port: 80 18 | targetPort: 12434 19 | protocol: TCP 20 | name: http 21 | selector: 22 | app: docker-model-runner 23 | {{ end }} 24 | -------------------------------------------------------------------------------- /templates/overlays/model-runner/model-runner-service.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ if .models }} 3 | --- 4 | #! model-runner-service.yaml 5 | # Generated code, do not edit 6 | apiVersion: v1 7 | kind: Service 8 | metadata: 9 | name: docker-model-runner 10 | namespace: {{ $project | safe }} 11 | labels: 12 | com.docker.compose.project: {{ $project }} 13 | com.docker.model.runner: "true" 14 | spec: 15 | type: ClusterIP 16 | ports: 17 | - port: 80 18 | targetPort: 12434 19 | protocol: TCP 20 | name: http 21 | selector: 22 | com.docker.compose.project: {{ $project }} 23 | com.docker.model.runner: "true" 24 | {{ end }} -------------------------------------------------------------------------------- /helm-templates/templates/model-runner-pvc.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ if .models }} 3 | --- 4 | #! model-runner-pvc.yaml 5 | apiVersion: v1 6 | kind: PersistentVolumeClaim 7 | metadata: 8 | name: model-storage 9 | namespace: {{ helmValue ".Values.namespace" }} 10 | labels: 11 | app: docker-model-runner 12 | com.docker.compose.project: {{ $project }} 13 | app.kubernetes.io/managed-by: Helm 14 | spec: 15 | accessModes: 16 | - ReadWriteOnce 17 | volumeMode: Filesystem 18 | storageClassName: {{ helmValue ".Values.modelRunner.storage.storageClass" }} 19 | resources: 20 | requests: 21 | storage: {{ helmValue ".Values.modelRunner.storage.size" }} 22 | {{ end }} 23 | -------------------------------------------------------------------------------- /templates/overlays/desktop/service.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ range $name, $service := .services }} 3 | {{ $publishedPort := false }} 4 | {{ if $service.ports }} 5 | # check if there is at least one published port 6 | {{ range $port := $service.ports}} 7 | {{ if $port.published }} 8 | {{ $publishedPort = true }} 9 | {{ break }} 10 | {{ end }} 11 | {{ end }} 12 | {{ end }} 13 | {{ if $publishedPort }} 14 | --- 15 | #! {{ $name }}-service.yaml 16 | # Generated code, do not edit 17 | apiVersion: v1 18 | kind: Service 19 | metadata: 20 | name: {{ $name | safe }}-published 21 | namespace: {{ $project | safe }} 22 | spec: 23 | type: LoadBalancer 24 | {{ end }} 25 | {{ end }} -------------------------------------------------------------------------------- /templates/base/expose.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ range $name, $service := .services }} 3 | {{ if $service.expose }} 4 | --- 5 | #! {{ $name }}-expose.yaml 6 | # Generated code, do not edit 7 | apiVersion: v1 8 | kind: Service 9 | metadata: 10 | name: {{ $name | safe }} 11 | namespace: {{ $project | safe }} 12 | labels: 13 | com.docker.compose.project: {{ $project }} 14 | com.docker.compose.service: {{ $name }} 15 | spec: 16 | selector: 17 | com.docker.compose.project: {{ $project }} 18 | com.docker.compose.service: {{ $name }} 19 | ports: 20 | {{ range $port := $service.expose}} 21 | - name: {{ portName $name $port }} 22 | port: {{ $port }} 23 | targetPort: {{ portName $name $port }} 24 | {{ end }} 25 | {{ end }} 26 | {{ end }} -------------------------------------------------------------------------------- /helm-templates/templates/networkpolicy.tmpl: -------------------------------------------------------------------------------- 1 | {{ range $name, $network := .networks }} 2 | --- 3 | #! {{ $name }}-network-policy.yaml 4 | # Generated code, do not edit 5 | apiVersion: networking.k8s.io/v1 6 | kind: NetworkPolicy 7 | metadata: 8 | name: {{ $name | safe }}-network-policy 9 | namespace: {{ helmValue ".Values.namespace" }} 10 | spec: 11 | podSelector: 12 | matchLabels: 13 | com.docker.compose.network.{{ $name }}: "true" 14 | policyTypes: 15 | - Ingress 16 | - Egress 17 | ingress: 18 | - from: 19 | - podSelector: 20 | matchLabels: 21 | com.docker.compose.network.{{ $name }}: "true" 22 | egress: 23 | - to: 24 | - podSelector: 25 | matchLabels: 26 | com.docker.compose.network.{{ $name }}: "true" 27 | {{ end }} -------------------------------------------------------------------------------- /templates/base/networkpolicy.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ range $name, $network := .networks }} 3 | --- 4 | #! {{ $name }}-network-policy.yaml 5 | # Generated code, do not edit 6 | apiVersion: networking.k8s.io/v1 7 | kind: NetworkPolicy 8 | metadata: 9 | name: {{ $name | safe }}-network-policy 10 | namespace: {{ $project | safe }} 11 | spec: 12 | podSelector: 13 | matchLabels: 14 | com.docker.compose.network.{{ $name }}: "true" 15 | policyTypes: 16 | - Ingress 17 | - Egress 18 | ingress: 19 | - from: 20 | - podSelector: 21 | matchLabels: 22 | com.docker.compose.network.{{ $name }}: "true" 23 | egress: 24 | - to: 25 | - podSelector: 26 | matchLabels: 27 | com.docker.compose.network.{{ $name }}: "true" 28 | {{ end }} -------------------------------------------------------------------------------- /templates/base/persistentVolumeClaim.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ range $name, $service := .services }} 3 | {{ range $volume := $service.volumes }} 4 | --- 5 | #! {{ $name }}-{{ $volume.source }}-persistentVolumeClaim.yaml 6 | # Generated code, do not edit 7 | apiVersion: v1 8 | kind: PersistentVolumeClaim 9 | metadata: 10 | name: {{ $name }}-{{ $volume.source | safe }} 11 | namespace: {{ $project | safe }} 12 | labels: 13 | com.docker.compose.service: {{ $name }} 14 | com.docker.compose.volume: {{ $volume.source }} 15 | spec: 16 | accessModes: 17 | # TODO would need to check which services use this volume 18 | - {{ if $volume.read_only }}ReadOnlyMany{{ else }}ReadWriteOnce{{ end }} 19 | volumeMode: Filesystem 20 | resources: 21 | requests: 22 | storage: 100Mi 23 | {{ end }} 24 | {{ end }} -------------------------------------------------------------------------------- /helm-templates/templates/expose.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ range $name, $service := .services }} 3 | {{ if $service.expose }} 4 | --- 5 | #! {{ $name }}-expose.yaml 6 | # Generated code, do not edit 7 | apiVersion: v1 8 | kind: Service 9 | metadata: 10 | name: {{ $name |safe }} 11 | namespace: {{ helmValue ".Values.namespace" }} 12 | labels: 13 | com.docker.compose.project: {{ $project }} 14 | com.docker.compose.service: {{ $name }} 15 | app.kubernetes.io/managed-by: Helm 16 | spec: 17 | selector: 18 | com.docker.compose.project: {{ $project }} 19 | com.docker.compose.service: {{ $name }} 20 | ports: 21 | {{ range $port := $service.expose}} 22 | - name: {{ portName $name $port }} 23 | port: {{ $port }} 24 | targetPort: {{ portName $name $port }} 25 | {{ end }} 26 | {{ end }} 27 | {{ end }} -------------------------------------------------------------------------------- /helm-templates/templates/persistentVolumeClaim.tmpl: -------------------------------------------------------------------------------- 1 | {{ range $name, $service := .services }} 2 | {{ range $volume := $service.volumes }} 3 | --- 4 | #! {{ $name }}-{{ $volume.source }}-persistentVolumeClaim.yaml 5 | # Generated code, do not edit 6 | apiVersion: v1 7 | kind: PersistentVolumeClaim 8 | metadata: 9 | labels: 10 | com.docker.compose.service: {{ $name }} 11 | com.docker.compose.volume: {{ $volume.source }} 12 | app.kubernetes.io/managed-by: Helm 13 | name: {{ $name | safe}}-{{ $volume.source | safe }} 14 | namespace: {{ helmValue ".Values.namespace" }} 15 | spec: 16 | accessModes: 17 | # TODO would need to check which services use this volume 18 | - {{ if $volume.read_only }}ReadOnlyMany{{ else }}{{ helmValue ".Values.storage.defaultAccessMode" }}{{ end }} 19 | volumeMode: Filesystem 20 | storageClassName: {{ helmValue ".Values.storage.defaultStorageClass" }} # see docker/desktop-storage-provisioner 21 | resources: 22 | requests: 23 | storage: {{ helmValue ".Values.storage.defaultSize" }} 24 | {{ end }} 25 | {{ end }} -------------------------------------------------------------------------------- /templates/overlays/desktop/deployment.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ range $name, $service := .services }} 3 | {{ if $service.models }} 4 | --- 5 | #! {{ $name }}-deployment.yaml 6 | # Generated code, do not edit 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | metadata: 10 | name: {{ $name | safe }} 11 | namespace: {{ $project | safe }} 12 | spec: 13 | template: 14 | spec: 15 | containers: 16 | - name: {{ if $service.container_name }}{{ $service.container_name | safe }}{{ else }}{{ $name | safe }}{{ end }} 17 | env: 18 | {{ range $key, $value := $service.models }} 19 | {{- if or (isString $value) (not $value) }} 20 | - name: {{ if isString $value }}{{ $value | uppercase }}{{ else }}{{ $key | uppercase }}{{ end }}_URL 21 | value: "http://host.docker.internal:12434/engines/v1/" 22 | {{- else }} 23 | - name: {{ if hasAttribute $value "endpoint_var" }}{{ getAttribute $value "endpoint_var" }}{{ else }}{{ $key | uppercase }}_URL{{ end }} 24 | value: "http://host.docker.internal:12434/engines/v1/" 25 | {{- end }} 26 | {{ end }} 27 | {{ end }} 28 | {{ end }} 29 | -------------------------------------------------------------------------------- /templates/base/service.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ range $name, $service := .services }} 3 | {{ $publishedPort := false }} 4 | {{ if $service.ports }} 5 | # check if there is at least one published port 6 | {{ range $port := $service.ports}} 7 | {{ if $port.published }} 8 | {{ $publishedPort = true }} 9 | {{ break }} 10 | {{ end }} 11 | {{ end }} 12 | {{ end }} 13 | {{ if $publishedPort }} 14 | --- 15 | #! {{ $name }}-service.yaml 16 | # Generated code, do not edit 17 | apiVersion: v1 18 | kind: Service 19 | metadata: 20 | name: {{ $name | safe }}-published 21 | namespace: {{ $project | safe }} 22 | labels: 23 | com.docker.compose.project: {{ $project }} 24 | com.docker.compose.service: {{ $name }} 25 | spec: 26 | selector: 27 | com.docker.compose.project: {{ $project }} 28 | com.docker.compose.service: {{ $name }} 29 | ports: 30 | {{ range $port := $service.ports}} 31 | {{ if $port.published }} 32 | - name: {{ if $port.name }}{{ slice $port.name 0 14 }}{{ else }}{{ portName $name $port.published }}{{ end }} 33 | port: {{ $port.published }} 34 | protocol: {{ $port.protocol | uppercase }} 35 | targetPort: {{ portName $name $port.target }} 36 | {{ end }} 37 | {{ end }} 38 | 39 | {{ end }} 40 | {{ end }} -------------------------------------------------------------------------------- /docker-bake.hcl: -------------------------------------------------------------------------------- 1 | 2 | # Special target: https://github.com/docker/metadata-action#bake-definition 3 | target "meta-helper" {} 4 | 5 | group "default" { 6 | targets = ["transformer", "kubernetes", "helm"] 7 | } 8 | 9 | target "_all-platforms" { 10 | platforms = [ 11 | "linux/386", 12 | "linux/amd64", 13 | "linux/arm/v6", 14 | "linux/arm/v7", 15 | "linux/arm64", 16 | "linux/ppc64le", 17 | ] 18 | } 19 | 20 | target "transformer" { 21 | inherits = ["meta-helper"] 22 | target = "transformer" 23 | } 24 | 25 | target "transformer_all" { 26 | inherits = ["transformer", "_all-platforms"] 27 | } 28 | 29 | target "transformer_local" { 30 | inherits = ["transformer"] 31 | tags = ["docker/compose-bridge-transformer"] 32 | } 33 | 34 | target "kubernetes" { 35 | inherits = ["meta-helper"] 36 | target = "kubernetes" 37 | } 38 | 39 | target "kubernetes_all" { 40 | inherits = ["kubernetes", "_all-platforms"] 41 | } 42 | 43 | target "kubernetes_local" { 44 | inherits = ["kubernetes"] 45 | tags = ["docker/compose-bridge-kubernetes"] 46 | } 47 | 48 | target "helm" { 49 | inherits = ["meta-helper"] 50 | target = "helm" 51 | } 52 | 53 | target "helm_all" { 54 | inherits = ["helm", "_all-platforms"] 55 | } 56 | 57 | target "helm_local" { 58 | inherits = ["helm"] 59 | tags = ["docker/compose-bridge-helm"] 60 | } -------------------------------------------------------------------------------- /helm-templates/templates/service.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ range $name, $service := .services }} 3 | {{ $publishedPort := false }} 4 | {{ if $service.ports }} 5 | # check if there is at least one published port 6 | {{ range $port := $service.ports}} 7 | {{ if $port.published }} 8 | {{ $publishedPort = true }} 9 | {{ break }} 10 | {{ end }} 11 | {{ end }} 12 | {{ end }} 13 | {{ if $publishedPort }} 14 | --- 15 | #! {{ $name }}-service.yaml 16 | # Generated code, do not edit 17 | apiVersion: v1 18 | kind: Service 19 | metadata: 20 | name: {{ $name | safe }}-published 21 | namespace: {{ helmValue ".Values.namespace" }} 22 | labels: 23 | com.docker.compose.project: {{ $project }} 24 | com.docker.compose.service: {{ $name }} 25 | app.kubernetes.io/managed-by: Helm 26 | spec: 27 | type: {{ helmValue ".Values.service.type" }} 28 | selector: 29 | com.docker.compose.project: {{ $project }} 30 | com.docker.compose.service: {{ $name }} 31 | ports: 32 | {{ range $port := $service.ports}} 33 | {{ if $port.published }} 34 | - name: {{ if $port.name }}{{ slice $port.name 0 14 }}{{ else }}{{ portName $name $port.published }}{{ end }} 35 | port: {{ $port.published }} 36 | protocol: {{ $port.protocol | uppercase }} 37 | targetPort: {{ portName $name $port.target }} 38 | {{ end }} 39 | {{ end }} 40 | 41 | {{ end }} 42 | {{ end }} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Compose Bridge Transformer Templates 2 | 3 | This repository contains the default Go templates used by the Docker Compose team to generate transformer images for Docker Desktop's Kubernetes cluster integration. 4 | 5 | ## Overview 6 | 7 | The repository provides: 8 | 9 | - **Base Transformer Image**: A minimal image containing the core transformation binary. 10 | - **Kubernetes Transformer Image**: Includes templates for generating Kubernetes manifests from Compose files. 11 | - **Helm Charts Transformer Image**: Includes templates for generating Helm charts from Compose files. 12 | 13 | ## Structure 14 | 15 | - `templates/`: Go templates for Kubernetes manifests. 16 | - `helm-templates/`: Go templates for Helm charts. 17 | - `Dockerfile`: Multi-stage build to produce the transformer images. 18 | 19 | ## Usage 20 | 21 | build the transfomer binary 22 | ```shell 23 | go build -o /go/bin/transform 24 | ``` 25 | 26 | build the transformer base image for local architecture 27 | ```shell 28 | docker bake -f docker-bake.hcl transformer_local 29 | ``` 30 | 31 | build the transformer base image for all architectures 32 | ```shell 33 | docker bake -f docker-bake.hcl transformer_all 34 | ``` 35 | 36 | build the kubernetes transformer image for local architecture 37 | ```shell 38 | docker bake -f docker-bake.hcl kubernetes_local 39 | ``` 40 | 41 | build the kubernetes transformer image for all architectures 42 | ```shell 43 | docker bake -f docker-bake.hcl kubernetes_all 44 | ``` 45 | 46 | build the helm transformer image for local architecture 47 | ```shell 48 | docker bake -f docker-bake.hcl helm_local 49 | ``` 50 | 51 | build the helm transformer image for all architectures 52 | ```shell 53 | docker bake -f docker-bake.hcl helm_all 54 | ``` 55 | 56 | ## Docker Desktop 57 | These templates are used internally by Docker Desktop to enable seamless conversion of Compose files to Kubernetes and deploy them into the internal Kubernetes cluster. 58 | 59 | ## License 60 | 61 | Licensed under the [Apache License 2.0](LICENSE). 62 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 180 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 7 9 | 10 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 11 | onlyLabels: [] 12 | 13 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 14 | exemptLabels: 15 | - "kind/feature" 16 | 17 | # Set to true to ignore issues in a project (defaults to false) 18 | exemptProjects: false 19 | 20 | # Set to true to ignore issues in a milestone (defaults to false) 21 | exemptMilestones: false 22 | 23 | # Set to true to ignore issues with an assignee (defaults to false) 24 | exemptAssignees: true 25 | 26 | # Label to use when marking as stale 27 | staleLabel: stale 28 | 29 | # Comment to post when marking as stale. Set to `false` to disable 30 | markComment: > 31 | This issue has been automatically marked as stale because it has not had 32 | recent activity. It will be closed if no further activity occurs. Thank you 33 | for your contributions. 34 | 35 | # Comment to post when removing the stale label. 36 | unmarkComment: > 37 | This issue has been automatically marked as not stale anymore due to the recent activity. 38 | 39 | # Comment to post when closing a stale Issue or Pull Request. 40 | closeComment: > 41 | This issue has been automatically closed because it had not recent activity during the stale period. 42 | 43 | # Limit the number of actions per hour, from 1-30. Default is 30 44 | limitPerRun: 30 45 | 46 | # Limit to only `issues` or `pulls` 47 | only: issues 48 | 49 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 50 | # pulls: 51 | # daysUntilStale: 30 52 | # markComment: > 53 | # This pull request has been automatically marked as stale because it has not had 54 | # recent activity. It will be closed if no further activity occurs. Thank you 55 | # for your contributions. 56 | 57 | # issues: 58 | # exemptLabels: 59 | # - confirmed 60 | -------------------------------------------------------------------------------- /helm-templates/values.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | #! values.yaml 3 | # Project Name 4 | projectName: {{ $project | safe }} 5 | 6 | # Namespace 7 | namespace: {{ $project | safe}} 8 | 9 | # Default deployment settings 10 | deployment: 11 | strategy: Recreate 12 | defaultReplicas: 1 13 | 14 | # Default resource limits 15 | resources: 16 | defaultCpuLimit: "100m" 17 | defaultMemoryLimit: "512Mi" 18 | 19 | # Service settings 20 | service: 21 | type: LoadBalancer 22 | 23 | # Storage settings 24 | storage: 25 | defaultStorageClass: "hostpath" 26 | defaultSize: "100Mi" 27 | defaultAccessMode: "ReadWriteOnce" 28 | 29 | {{ if .models }} 30 | # Model Runner settings 31 | modelRunner: 32 | # Set to false for Docker Desktop (uses host instance) 33 | # Set to true for standalone Kubernetes clusters 34 | enabled: false 35 | 36 | # Endpoint used when enabled=false (Docker Desktop) 37 | hostEndpoint: "http://host.docker.internal:12434/engines/v1/" 38 | 39 | # Deployment settings when enabled=true 40 | image: "docker/model-runner:latest" 41 | imagePullPolicy: "IfNotPresent" 42 | 43 | # GPU support 44 | gpu: 45 | enabled: false 46 | vendor: "nvidia" # nvidia or amd 47 | count: 1 48 | 49 | # Node scheduling (uncomment and customize as needed) 50 | # nodeSelector: 51 | # accelerator: nvidia-tesla-t4 52 | # tolerations: [] 53 | # affinity: {} 54 | 55 | # Security context 56 | securityContext: 57 | allowPrivilegeEscalation: false 58 | 59 | # Environment variables (uncomment and add as needed) 60 | # env: 61 | # DMR_ORIGINS: "http://localhost:31246" 62 | 63 | resources: 64 | limits: 65 | cpu: "1000m" 66 | memory: "2Gi" 67 | requests: 68 | cpu: "100m" 69 | memory: "256Mi" 70 | 71 | # Storage for models 72 | storage: 73 | size: "100Gi" 74 | storageClass: "" # Empty uses default storage class 75 | 76 | # Models to pre-pull 77 | models:{{ range $name, $model := .models }} 78 | - {{ $model.model }}{{ end }} 79 | {{ end }} 80 | 81 | # Services variables 82 | {{ range $name, $service := .services }} 83 | {{ $name }}: 84 | image: {{ if $service.image }}{{ $service.image }}{{ else }}{{ $project | safe }}-{{ $name | safe}}{{ end }} 85 | imagePullPolicy: {{ if $service.pull_policy }}{{ $service.pull_policy | title }}{{ else }}IfNotPresent{{ end }} 86 | 87 | {{ end }} 88 | 89 | # You can apply the same logic to loop on networks, volumes, secrets and configs... 90 | -------------------------------------------------------------------------------- /templates/overlays/model-runner/model-runner-deployment.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ if .models }} 3 | --- 4 | #! model-runner-deployment.yaml 5 | # Generated code, do not edit 6 | apiVersion: apps/v1 7 | kind: Deployment 8 | metadata: 9 | name: docker-model-runner 10 | namespace: {{ $project | safe }} 11 | labels: 12 | com.docker.compose.project: {{ $project }} 13 | com.docker.model.runner: "true" 14 | spec: 15 | replicas: 1 16 | selector: 17 | matchLabels: 18 | com.docker.compose.project: {{ $project }} 19 | com.docker.model.runner: "true" 20 | strategy: 21 | type: Recreate 22 | template: 23 | metadata: 24 | labels: 25 | com.docker.compose.project: {{ $project }} 26 | com.docker.model.runner: "true" 27 | spec: 28 | restartPolicy: Always 29 | # Uncomment to schedule on specific nodes (e.g., GPU nodes) 30 | # nodeSelector: 31 | # accelerator: nvidia-tesla-t4 32 | # Uncomment to tolerate tainted nodes 33 | # tolerations: 34 | # - key: "nvidia.com/gpu" 35 | # operator: "Exists" 36 | # effect: "NoSchedule" 37 | initContainers: 38 | - name: fix-permissions 39 | image: busybox:1.35 40 | command: ["sh", "-c", "chmod a+rwx /models"] 41 | volumeMounts: 42 | - name: model-storage 43 | mountPath: /models 44 | containers: 45 | - name: model-runner 46 | image: docker/model-runner:latest 47 | imagePullPolicy: IfNotPresent 48 | securityContext: 49 | allowPrivilegeEscalation: false 50 | ports: 51 | - containerPort: 12434 52 | volumeMounts: 53 | - name: model-storage 54 | mountPath: /models 55 | resources: 56 | limits: 57 | cpu: 1000m 58 | memory: 2Gi 59 | # Uncomment for GPU support (requires device plugin) 60 | # nvidia.com/gpu: "1" # For NVIDIA GPUs 61 | # amd.com/gpu: "1" # For AMD GPUs 62 | requests: 63 | cpu: 100m 64 | memory: 256Mi 65 | # Uncomment for GPU support (must match limits) 66 | # nvidia.com/gpu: "1" 67 | # amd.com/gpu: "1" 68 | readinessProbe: 69 | httpGet: 70 | path: /engines/status 71 | port: 12434 72 | initialDelaySeconds: 5 73 | periodSeconds: 10 74 | failureThreshold: 3 75 | livenessProbe: 76 | httpGet: 77 | path: /engines/status 78 | port: 12434 79 | initialDelaySeconds: 15 80 | periodSeconds: 20 81 | failureThreshold: 3 82 | # Model pre-pulling sidecar (comment out to disable) 83 | - name: model-init 84 | image: curlimages/curl:8.14.1 85 | command: ["/bin/sh", "-c"] 86 | args: 87 | - | 88 | set -ex 89 | MODEL_RUNNER=http://localhost:12434 90 | echo "Pre-pulling models..." 91 | while IFS= read -r model; do 92 | if [ -n "$model" ]; then 93 | echo "Pulling model: $model" 94 | curl -d "{\"from\": \"$model\"}" "$MODEL_RUNNER"/models/create 95 | fi 96 | done < /config/models 97 | echo "Model pre-pull complete" 98 | tail -f /dev/null 99 | volumeMounts: 100 | - name: model-storage 101 | mountPath: /models 102 | - name: init-config 103 | mountPath: /config 104 | volumes: 105 | - name: model-storage 106 | # Default: Persistent storage (survives pod restarts) 107 | persistentVolumeClaim: 108 | claimName: model-storage 109 | # Alternative: Ephemeral storage (faster startup, lower cost) 110 | # Uncomment below and remove persistentVolumeClaim above 111 | # emptyDir: 112 | # sizeLimit: 100Gi 113 | - name: init-config 114 | configMap: 115 | name: docker-model-runner-init 116 | {{ end }} -------------------------------------------------------------------------------- /helm-templates/templates/model-runner-deployment.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ if .models }} 3 | --- 4 | #! model-runner-deployment.yaml 5 | apiVersion: apps/v1 6 | kind: Deployment 7 | metadata: 8 | name: docker-model-runner 9 | namespace: {{ helmValue ".Values.namespace" }} 10 | labels: 11 | app: docker-model-runner 12 | com.docker.compose.project: {{ $project }} 13 | app.kubernetes.io/managed-by: Helm 14 | spec: 15 | replicas: {{ helmValue "if .Values.modelRunner.enabled" }}1{{ helmValue "else" }}0{{ helmValue "end" }} 16 | selector: 17 | matchLabels: 18 | app: docker-model-runner 19 | strategy: 20 | type: Recreate 21 | template: 22 | metadata: 23 | labels: 24 | app: docker-model-runner 25 | com.docker.compose.project: {{ $project }} 26 | spec: 27 | # Uncomment to schedule on specific nodes (e.g., GPU nodes) 28 | # nodeSelector: {{ helmValue ".Values.modelRunner.nodeSelector" }} 29 | # Uncomment to tolerate tainted nodes 30 | # tolerations: {{ helmValue ".Values.modelRunner.tolerations" }} 31 | # Uncomment for advanced pod scheduling 32 | # affinity: {{ helmValue ".Values.modelRunner.affinity" }} 33 | initContainers: 34 | - name: fix-permissions 35 | image: busybox:1.35 36 | command: ["sh", "-c", "chmod a+rwx /models"] 37 | volumeMounts: 38 | - name: model-storage 39 | mountPath: /models 40 | containers: 41 | - name: model-runner 42 | image: {{ helmValue ".Values.modelRunner.image" }} 43 | imagePullPolicy: {{ helmValue ".Values.modelRunner.imagePullPolicy" }} 44 | securityContext: 45 | allowPrivilegeEscalation: {{ helmValue ".Values.modelRunner.securityContext.allowPrivilegeEscalation" }} 46 | ports: 47 | - name: http 48 | containerPort: 12434 49 | volumeMounts: 50 | - name: model-storage 51 | mountPath: /models 52 | resources: 53 | limits: 54 | cpu: {{ helmValue ".Values.modelRunner.resources.limits.cpu" }} 55 | memory: {{ helmValue ".Values.modelRunner.resources.limits.memory" }} 56 | {{ helmValue "if eq .Values.modelRunner.gpu.vendor \"nvidia\"" }}nvidia.com/gpu{{ helmValue "else" }}amd.com/gpu{{ helmValue "end" }}: {{ helmValue "if .Values.modelRunner.gpu.enabled" }}{{ helmValue ".Values.modelRunner.gpu.count" }}{{ helmValue "else" }}0{{ helmValue "end" }} 57 | requests: 58 | cpu: {{ helmValue ".Values.modelRunner.resources.requests.cpu" }} 59 | memory: {{ helmValue ".Values.modelRunner.resources.requests.memory" }} 60 | {{ helmValue "if eq .Values.modelRunner.gpu.vendor \"nvidia\"" }}nvidia.com/gpu{{ helmValue "else" }}amd.com/gpu{{ helmValue "end" }}: {{ helmValue "if .Values.modelRunner.gpu.enabled" }}{{ helmValue ".Values.modelRunner.gpu.count" }}{{ helmValue "else" }}0{{ helmValue "end" }} 61 | readinessProbe: 62 | httpGet: 63 | path: /engines/status 64 | port: 12434 65 | initialDelaySeconds: 5 66 | periodSeconds: 10 67 | failureThreshold: 3 68 | livenessProbe: 69 | httpGet: 70 | path: /engines/status 71 | port: 12434 72 | initialDelaySeconds: 15 73 | periodSeconds: 20 74 | failureThreshold: 3 75 | - name: model-init 76 | image: curlimages/curl:8.14.1 77 | command: ["/bin/sh", "-c"] 78 | args: 79 | - | 80 | set -ex 81 | MODEL_RUNNER=http://localhost:12434 82 | echo "Pre-pulling models..." 83 | {{ helmValue "range .Values.modelRunner.models" }} 84 | echo "Pulling model: {{ helmValue "." }}" 85 | curl -d "{\"from\": \"{{ helmValue "." }}\"}" "$MODEL_RUNNER"/models/create 86 | {{ helmValue "end" }} 87 | echo "Model pre-pull complete" 88 | tail -f /dev/null 89 | volumeMounts: 90 | - name: model-storage 91 | mountPath: /models 92 | volumes: 93 | - name: model-storage 94 | persistentVolumeClaim: 95 | claimName: model-storage 96 | {{ end }} 97 | -------------------------------------------------------------------------------- /.github/workflows/transformer-images.yaml: -------------------------------------------------------------------------------- 1 | name: Transformer Images 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | branches: 10 | - 'main' 11 | tags: 12 | - 'v*' 13 | pull_request: 14 | 15 | jobs: 16 | transformer-image: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | - name: Login to DockerHub 22 | if: github.event_name != 'pull_request' 23 | uses: docker/login-action@v3 24 | with: 25 | username: ${{ vars.DOCKERPUBLICBOT_USERNAME }} 26 | password: ${{ secrets.DOCKERPUBLICBOT_WRITE_PAT }} 27 | - name: Set up QEMU 28 | uses: docker/setup-qemu-action@v3 29 | - name: Set up Docker Buildx 30 | uses: docker/setup-buildx-action@v3 31 | with: 32 | version: "lab:latest" 33 | - name: Docker meta 34 | id: meta 35 | uses: docker/metadata-action@v5 36 | with: 37 | images: | 38 | docker/compose-bridge-transformer 39 | tags: | 40 | type=ref,event=tag 41 | type=edge 42 | type=ref,event=pr 43 | bake-target: meta-helper 44 | - name: Build and push image 45 | uses: docker/bake-action@v6 46 | id: bake 47 | with: 48 | source: . 49 | files: | 50 | ./docker-bake.hcl 51 | cwd://${{ steps.meta.outputs.bake-file }} 52 | targets: transformer_all 53 | push: ${{ github.event_name != 'pull_request' }} 54 | sbom: true 55 | provenance: mode=max 56 | set: | 57 | *.cache-from=type=gha,scope=transformer-images 58 | *.cache-to=type=gha,scope=transformer-images,mode=max 59 | kubernetes-image: 60 | runs-on: ubuntu-latest 61 | needs: 62 | - transformer-image 63 | steps: 64 | - name: Checkout 65 | uses: actions/checkout@v4 66 | - name: Login to DockerHub 67 | if: github.event_name != 'pull_request' 68 | uses: docker/login-action@v3 69 | with: 70 | username: ${{ vars.DOCKERPUBLICBOT_USERNAME }} 71 | password: ${{ secrets.DOCKERPUBLICBOT_WRITE_PAT }} 72 | - name: Set up QEMU 73 | uses: docker/setup-qemu-action@v3 74 | - name: Set up Docker Buildx 75 | uses: docker/setup-buildx-action@v3 76 | with: 77 | version: "lab:latest" 78 | - name: Docker meta 79 | id: meta 80 | uses: docker/metadata-action@v5 81 | with: 82 | images: | 83 | docker/compose-bridge-kubernetes 84 | tags: | 85 | type=ref,event=tag 86 | type=edge 87 | type=ref,event=pr 88 | bake-target: meta-helper 89 | - name: Build and push image 90 | uses: docker/bake-action@v6 91 | id: bake 92 | with: 93 | source: . 94 | files: | 95 | ./docker-bake.hcl 96 | cwd://${{ steps.meta.outputs.bake-file }} 97 | targets: kubernetes_all 98 | push: ${{ github.event_name != 'pull_request' }} 99 | sbom: true 100 | provenance: mode=max 101 | set: | 102 | *.cache-from=type=gha,scope=transformer-images 103 | *.cache-to=type=gha,scope=transformer-images,mode=max 104 | helm-image: 105 | runs-on: ubuntu-latest 106 | needs: 107 | - transformer-image 108 | steps: 109 | - name: Checkout 110 | uses: actions/checkout@v4 111 | - name: Login to DockerHub 112 | if: github.event_name != 'pull_request' 113 | uses: docker/login-action@v3 114 | with: 115 | username: ${{ vars.DOCKERPUBLICBOT_USERNAME }} 116 | password: ${{ secrets.DOCKERPUBLICBOT_WRITE_PAT }} 117 | - name: Set up QEMU 118 | uses: docker/setup-qemu-action@v3 119 | - name: Set up Docker Buildx 120 | uses: docker/setup-buildx-action@v3 121 | with: 122 | version: "lab:latest" 123 | - name: Docker meta 124 | id: meta 125 | uses: docker/metadata-action@v5 126 | with: 127 | images: | 128 | docker/compose-bridge-helm 129 | tags: | 130 | type=ref,event=tag 131 | type=edge 132 | type=ref,event=pr 133 | bake-target: meta-helper 134 | - name: Build and push image 135 | uses: docker/bake-action@v6 136 | id: bake 137 | with: 138 | source: . 139 | files: | 140 | ./docker-bake.hcl 141 | cwd://${{ steps.meta.outputs.bake-file }} 142 | targets: helm_all 143 | push: ${{ github.event_name != 'pull_request' }} 144 | sbom: true 145 | provenance: mode=max 146 | set: | 147 | *.cache-from=type=gha,scope=transformer-images 148 | *.cache-to=type=gha,scope=transformer-images,mode=max 149 | 150 | release: 151 | runs-on: ubuntu-latest 152 | permissions: 153 | contents: write # for release-action 154 | steps: 155 | - 156 | name: GitHub Release 157 | if: startsWith(github.ref, 'refs/tags/v') 158 | uses: ncipollo/release-action@v1.16.0 159 | with: 160 | generateReleaseNotes: true 161 | draft: true 162 | token: ${{ secrets.GITHUB_TOKEN }} 163 | -------------------------------------------------------------------------------- /convert.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/base64" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "io/fs" 11 | "os" 12 | "path/filepath" 13 | "strconv" 14 | "strings" 15 | "text/template" 16 | "time" 17 | 18 | "go.yaml.in/yaml/v3" 19 | ) 20 | 21 | func main() { 22 | raw, err := os.ReadFile("/in/compose.yaml") 23 | if err != nil { 24 | fmt.Fprintln(os.Stderr, "failed to read compose file /in/compose.yaml") 25 | fmt.Fprintln(os.Stderr, err.Error()) 26 | os.Exit(1) 27 | } 28 | 29 | var model map[string]any 30 | err = yaml.Unmarshal(raw, &model) 31 | if err != nil { 32 | fmt.Fprintln(os.Stderr, "failed to parse compose file /in/compose.yaml") 33 | fmt.Fprintln(os.Stderr, err.Error()) 34 | os.Exit(1) 35 | } 36 | 37 | err = Convert(model, "/templates", "/out") 38 | if err != nil { 39 | fmt.Fprintln(os.Stderr, "failed to apply template") 40 | fmt.Fprintln(os.Stderr, err.Error()) 41 | os.Exit(1) 42 | } 43 | } 44 | 45 | func Convert(model map[string]any, templateDir string, out string) error { 46 | dir, err := os.ReadDir(templateDir) 47 | if err != nil { 48 | return fmt.Errorf("cannot access templates dir: %w", err) 49 | } 50 | for _, entry := range dir { 51 | if entry.Name() == "_index.tmpl" { 52 | continue 53 | } 54 | f := filepath.Join(templateDir, entry.Name()) 55 | newOut := filepath.Join(out, entry.Name()) 56 | if entry.IsDir() { 57 | // Create directory lazily - it will be created when files are written 58 | err := os.MkdirAll(newOut, fs.ModePerm) 59 | if err != nil && !os.IsExist(err) { 60 | return err 61 | } 62 | if err := Convert(model, f, newOut); err != nil { 63 | return err 64 | } 65 | // Clean up empty directories 66 | if isEmpty, _ := isDirEmpty(newOut); isEmpty { 67 | os.Remove(newOut) 68 | } 69 | continue 70 | } 71 | err := applyTemplate(model, f, out) 72 | if err != nil { 73 | return err 74 | } 75 | } 76 | index := filepath.Join(templateDir, "_index.tmpl") 77 | if _, err := os.Stat(index); err == nil { 78 | files, err := os.ReadDir(out) 79 | if err != nil { 80 | return err 81 | } 82 | err = applyTemplate(files, index, out) 83 | if err != nil { 84 | return err 85 | } 86 | } 87 | return nil 88 | } 89 | 90 | func isDirEmpty(path string) (bool, error) { 91 | entries, err := os.ReadDir(path) 92 | if err != nil { 93 | return false, err 94 | } 95 | return len(entries) == 0, nil 96 | } 97 | 98 | func applyTemplate(model any, file string, output string) error { 99 | tmpl, err := template.New(filepath.Base(file)).Funcs(helpers).ParseFiles(file) 100 | if err != nil { 101 | ExitError("cannot parse template "+file, err) 102 | } 103 | 104 | buff := bytes.Buffer{} 105 | err = tmpl.Execute(&buff, model) 106 | if err != nil { 107 | ExitError("cannot execute template "+file, err) 108 | } 109 | 110 | decoder := yaml.NewDecoder(&buff) 111 | for { 112 | var doc yaml.Node 113 | err := decoder.Decode(&doc) 114 | if err == io.EOF { 115 | break 116 | } 117 | if err != nil { 118 | ExitError("failed to parse generated yaml "+file, err) 119 | } 120 | 121 | out := bytes.Buffer{} 122 | encoder := yaml.NewEncoder(&out) 123 | err = encoder.Encode(&doc) 124 | if err != nil { 125 | ExitError("failed to parse generated yaml "+file, err) 126 | } 127 | cleanOut := strings.ReplaceAll(out.String(), "⌦", "{{") 128 | cleanOut = strings.ReplaceAll(cleanOut, "⌫", "}}") 129 | 130 | fileOut := fileComment(&doc) 131 | if fileOut != "" { 132 | f := filepath.Join(output, fileOut) 133 | os.WriteFile(f, []byte(cleanOut), 0o700) 134 | fmt.Printf("Kubernetes resource \033[32;1m%s\033[0;m created\n", fileOut) 135 | } else { 136 | fmt.Println(cleanOut) 137 | } 138 | } 139 | return nil 140 | } 141 | 142 | func fileComment(node *yaml.Node) string { 143 | if node.HeadComment == "" { 144 | if len(node.Content) > 0 { 145 | return fileComment(node.Content[0]) 146 | } 147 | return "" 148 | } 149 | for _, s := range strings.Split(node.HeadComment, "\n") { 150 | s := strings.TrimSpace(s) 151 | if strings.HasPrefix(s, "#! ") { 152 | return s[3:] 153 | } 154 | } 155 | return "" 156 | } 157 | 158 | var helpers = map[string]any{ 159 | "helmValue": func(s string, args ...any) string { 160 | return fmt.Sprintf("⌦ %s ⌫", fmt.Sprintf(s, args...)) 161 | }, 162 | "isString": func(v any) bool { 163 | _, ok := v.(string) 164 | return ok 165 | }, 166 | "hasAttribute": func(m any, attribute string) bool { 167 | if m == nil { 168 | return false 169 | } 170 | mapValue, ok := m.(map[string]any) 171 | if !ok { 172 | return false 173 | } 174 | _, exists := mapValue[attribute] 175 | return exists 176 | }, 177 | "getAttribute": func(m any, attribute string) any { 178 | if m == nil { 179 | return nil 180 | } 181 | mapValue, ok := m.(map[string]any) 182 | if !ok { 183 | return nil 184 | } 185 | return mapValue[attribute] 186 | }, 187 | "required": func(attr string, a any) any { 188 | if a != nil { 189 | return a 190 | } 191 | ExitError("missing required attribute in compose model", errors.New(attr)) 192 | return nil 193 | }, 194 | "seconds": func(s any) float64 { 195 | duration, _ := time.ParseDuration(s.(string)) 196 | return duration.Seconds() 197 | }, 198 | "uppercase": func(s string) string { 199 | return strings.ToUpper(s) 200 | }, 201 | "title": func(s string) string { 202 | return strings.Title(s) 203 | }, 204 | "safe": func(s string) string { 205 | return safe(s) 206 | }, 207 | "truncate": func(n int, s []any) []any { 208 | return s[n:] 209 | }, 210 | "join": func(sep string, s []any) string { 211 | var ss []string 212 | for _, a := range s { 213 | ss = append(ss, a.(string)) 214 | } 215 | return strings.Join(ss, sep) 216 | }, 217 | "base64": func(s string) string { 218 | return base64.StdEncoding.EncodeToString([]byte(s)) 219 | }, 220 | "readfile": func(s string) string { 221 | file, err := os.ReadFile(s) 222 | if err != nil { 223 | ExitError("failed to read "+s, err) 224 | } 225 | return string(file) 226 | }, 227 | "getenv": func(s string) string { 228 | return os.Getenv(s) 229 | }, 230 | "dir": func(s string) string { 231 | return filepath.Dir(s) 232 | }, 233 | "indent": func(s string, indent int) string { 234 | indentation := strings.Repeat(" ", indent) 235 | lines := strings.Builder{} 236 | sc := bufio.NewScanner(strings.NewReader(s)) 237 | for sc.Scan() { 238 | lines.WriteString(indentation) 239 | lines.WriteString(sc.Text()) 240 | lines.WriteString("\n") 241 | } 242 | return lines.String() 243 | }, 244 | "map": func(s string, rules ...string) string { 245 | for _, rule := range rules { 246 | before, after, _ := strings.Cut(rule, "->") 247 | if s == strings.TrimSpace(before) { 248 | return strings.TrimSpace(after) 249 | } 250 | } 251 | return s 252 | }, 253 | "portName": func(service string, port any) string { 254 | var portAsString string 255 | switch port.(type) { 256 | case string: 257 | portAsString = port.(string) 258 | break 259 | case int: 260 | portAsString = strconv.Itoa(port.(int)) 261 | break 262 | } 263 | shrinkTo := 15 - (len(portAsString) + 1) 264 | if len(service) < shrinkTo { 265 | shrinkTo = len(service) 266 | } 267 | return safe(fmt.Sprintf("%s-%s", service[0:shrinkTo], portAsString)) 268 | }, 269 | } 270 | 271 | func safe(s string) string { 272 | s = strings.ToLower(s) 273 | s = strings.Map(func(r rune) rune { 274 | if ('a' <= r && r <= 'z') || 275 | ('A' <= r && r <= 'Z') || 276 | ('0' <= r && r <= '9') { 277 | return r 278 | } 279 | return '-' 280 | }, s) 281 | for len(s) > 0 && s[0] == '-' { 282 | s = s[1:] 283 | } 284 | return s 285 | } 286 | 287 | func ExitError(message string, err error) { 288 | fmt.Fprintf(os.Stderr, "%s: %s\n", message, err) 289 | os.Exit(1) 290 | } 291 | -------------------------------------------------------------------------------- /templates/base/deployment.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ range $name, $service := .services }} 3 | --- 4 | #! {{ $name }}-deployment.yaml 5 | # Generated code, do not edit 6 | apiVersion: apps/v1 7 | {{ if eq $service.deploy.mode "global" }} 8 | kind: DaemonSet 9 | {{ else }} 10 | kind: Deployment 11 | {{ end }} 12 | metadata: 13 | name: {{ $name | safe }} 14 | namespace: {{ $project | safe }} 15 | labels: 16 | com.docker.compose.project: {{ $project }} 17 | com.docker.compose.service: {{ $name }} 18 | spec: 19 | replicas: {{ if $service.scale }} {{ $service.scale }} {{ else }} 1 {{ end }} 20 | selector: 21 | matchLabels: 22 | com.docker.compose.project: {{ $project }} 23 | com.docker.compose.service: {{$name}} 24 | strategy: 25 | type: Recreate 26 | template: 27 | metadata: 28 | labels: 29 | com.docker.compose.project: {{ $project }} 30 | com.docker.compose.service: {{ $name }} 31 | {{ range $name, $config := $service.networks}} 32 | com.docker.compose.network.{{ $name }}: "true" 33 | {{ end }} 34 | spec: 35 | {{ if $service.restart }} 36 | restartPolicy : {{ map $service.restart "always->Always" "on-failure->OnFailure" "no->Never"}} 37 | {{ end }} 38 | containers: 39 | - name: {{ if $service.container_name }}{{ $service.container_name | safe }}{{ else }}{{ $name | safe }}{{ end }} 40 | image: {{ if $service.image }}{{ $service.image }}{{ else }}{{ $project }}-{{ $name }}{{ end }} 41 | imagePullPolicy: {{ if $service.pull_policy }}{{ $service.pull_policy | title }}{{ else }}IfNotPresent{{ end }} 42 | {{ if $service.entrypoint }} 43 | command: 44 | {{ range $service.entrypoint }} 45 | - {{ . }} 46 | {{ end }}{{ end }} 47 | {{ if $service.command }} 48 | args: 49 | {{ range $service.command }} 50 | - {{ . }} 51 | {{ end }}{{ end }} 52 | {{ if $service.working_dir }} 53 | workingDir: {{ $service.working_dir }}{{ end }} 54 | {{ if or $service.environment $service.models }} 55 | env: 56 | {{ range $key, $value := $service.environment }} 57 | - name: {{ $key }} 58 | value: {{ printf "%q" $value }}{{ end }} 59 | {{ range $key, $value := $service.models }} 60 | {{- if or (isString $value) (not $value) }} 61 | - name: {{ if isString $value }}{{ $value | uppercase }}{{ else }}{{ $key | uppercase }}{{ end }}_URL 62 | value: "http://docker-model-runner/engines/v1/" 63 | - name: {{ if isString $value }}{{ $value | uppercase }}{{ else }}{{ $key | uppercase }}{{ end }}_MODEL 64 | value: "{{ if isString $value }}{{ or (index $.models $value).model $value }}{{ else }}{{ or (index $.models $key).model $key }}{{ end }}" 65 | {{- else }} 66 | - name: {{ if hasAttribute $value "endpoint_var" }}{{ getAttribute $value "endpoint_var" }}{{ else }}{{ $key | uppercase }}_URL{{ end }} 67 | value: "http://docker-model-runner/engines/v1/" 68 | - name: {{ if hasAttribute $value "model_var" }}{{ getAttribute $value "model_var" }}{{ else }}{{ $key | uppercase }}_MODEL{{ end }} 69 | value: "{{ or (index $.models $key).model $key }}" 70 | {{- end }}{{ end }} 71 | {{ end }} 72 | 73 | {{ if or $service.user $service.group_add $service.sysctls $service.read_only $service.privileged $service.cap_add $service.cap_drop }} 74 | securityContext: 75 | {{ if $service.user }} 76 | runAsUser: {{ $service.user }} {{ end }} 77 | {{ if $service.group_add }} 78 | supplementalGroups: {{ $service.group_add }} {{ end }} 79 | {{ if $service.sysctls }} 80 | sysctls: 81 | {{ range $name, $value := $service.sysctls }} 82 | name: {{ $name }} 83 | value: {{ $value }} 84 | {{ end }} 85 | {{ end }} 86 | 87 | {{ if $service.read_only }} 88 | readOnlyRootFilesystem: {{ $service.read_only }} {{ end }} 89 | {{ if $service.privileged }} 90 | privileged: {{ $service.privileged }} {{ end }} 91 | {{ if or $service.cap_add $service.cap_drop }} 92 | capabilities: 93 | {{ if $service.cap_add }} 94 | add: {{ $service.cap_add }} {{ end }} 95 | {{ if $service.cap_drop }} 96 | drop: {{ $service.cap_drop }} {{ end }} 97 | {{ end }} 98 | {{ end }} 99 | 100 | {{ if or $service.cpu $service.memory }} 101 | resources: 102 | limits: 103 | {{ if $service.cpus }} 104 | cpu: {{ $service.cpus }} {{ end }} 105 | {{ if $service.memory }} 106 | memory: {{ $service.memory }} {{ end }} 107 | {{ end }} 108 | 109 | {{ if $service.healthcheck }} 110 | livenessProbe: 111 | exec: 112 | {{ if eq "CMD-SHELL" (index $service.healthcheck.test 0)}} 113 | command: 114 | - /bin/sh 115 | - -c 116 | - {{ $service.healthcheck.test | truncate 1 | join " "}} 117 | {{ else }} 118 | command: {{ $service.healthcheck.test | truncate 1 }} 119 | {{ end }} 120 | {{ if $service.healthcheck.interval }} 121 | periodSeconds: {{ $service.healthcheck.interval | seconds }}{{ end }} 122 | {{ if $service.healthcheck.start_period }} 123 | initialDelaySeconds: {{ $service.healthcheck.start_period | seconds }}{{ end }} 124 | {{ if $service.healthcheck.timeout }} 125 | timeoutSeconds: {{ $service.healthcheck.timeout | seconds }}{{ end }} 126 | {{ if $service.healthcheck.retries }} 127 | failureThreshold: {{ $service.healthcheck.retries }}{{ end }} 128 | {{ end }} 129 | 130 | 131 | {{ if $service.expose }} 132 | ports: 133 | {{ range $port := $service.expose }} 134 | - name: {{ portName $name $port }} 135 | containerPort: {{ $port }} 136 | {{ end }} 137 | {{ end }} 138 | 139 | {{ if or $service.volumes $service.secrets $service.configs }} 140 | volumeMounts: 141 | {{ range $volume := $service.volumes }} 142 | - name: {{ $volume.target | safe }} 143 | mountPath: {{ $volume.target }} 144 | {{ if $volume.read_only }} 145 | readOnly: true{{ end }} 146 | {{ end }} 147 | {{ range $secret := $service.secrets }} 148 | - name: {{ $secret.target | safe }} 149 | mountPath: {{ $secret.target }} 150 | subPath: {{ $secret.source }} 151 | readOnly: true 152 | {{ end }} 153 | {{ range $config := $service.configs }} 154 | - name: {{ $config.target | safe }} 155 | mountPath: {{ $config.target }} 156 | subPath: {{ $config.source }} 157 | readOnly: true 158 | {{ end }} 159 | {{ end }} 160 | 161 | 162 | {{ if or $service.dns $service.dns_search $service.dns_opt }} 163 | dnsConfig: 164 | {{ if $service.dns }} 165 | nameservers: 166 | {{ range $service.dns }} 167 | - {{ . }} 168 | {{ end }} 169 | {{ end }} 170 | {{ if $service.dns_search }} 171 | searches: 172 | {{ range $service.dns_search }} 173 | - {{ . }} 174 | {{ end }} 175 | {{ end }} 176 | {{ if $service.dns_opt }} 177 | options: 178 | {{ range $service.dns_opt }} 179 | - name: {{ . }} 180 | {{ end }} 181 | {{ end }} 182 | {{ end }} 183 | 184 | 185 | {{ if or $service.volumes $service.secrets $service.configs }} 186 | volumes: 187 | {{ range $secret := $service.secrets }} 188 | - name: {{ $secret.target | safe }} 189 | secret: 190 | secretName: {{ $secret.source | safe}} 191 | items: 192 | - key: {{ $secret.source }} 193 | path: {{ $secret.source }} 194 | {{ end }} 195 | 196 | {{ range $config := $service.configs }} 197 | - name: {{ $config.target | safe }} 198 | configMap: 199 | name: {{ $project | safe }} 200 | items: 201 | - key: {{ $config.source }} 202 | path: {{ $config.source }} 203 | {{ end }} 204 | 205 | {{ range $volume := $service.volumes }} 206 | - name: {{ $volume.target | safe }} 207 | {{ if eq $volume.type "volume" }} 208 | persistentVolumeClaim: 209 | claimName: {{$name | safe }}-{{ $volume.source | safe }} 210 | {{ else if eq $volume.type "bind" }} 211 | hostPath: 212 | path: {{ $volume.source }} 213 | {{ else if eq $volume.type "tmpfs" }} 214 | emptyDir: 215 | {{ if $volume.tmpfs.size }} 216 | sizeLimit: {{ $volume.tmpfs.size }}{{ end }} 217 | {{ end }} 218 | 219 | {{ if $volume.read_only }} 220 | readOnly: true{{ end }} 221 | 222 | {{ end }} 223 | {{ end }} 224 | {{ end }} 225 | -------------------------------------------------------------------------------- /helm-templates/templates/deployment.tmpl: -------------------------------------------------------------------------------- 1 | {{ $project := .name }} 2 | {{ range $name, $service := .services }} 3 | --- 4 | #! {{ $name }}-deployment.yaml 5 | # Generated code, do not edit 6 | apiVersion: apps/v1 7 | {{ if eq $service.deploy.mode "global" }} 8 | kind: DaemonSet 9 | {{ else }} 10 | kind: Deployment 11 | {{ end }} 12 | metadata: 13 | name: {{ $name | safe }} 14 | namespace: {{ helmValue ".Values.namespace" }} 15 | labels: 16 | com.docker.compose.project: {{ $project }} 17 | com.docker.compose.service: {{ $name }} 18 | app.kubernetes.io/managed-by: Helm 19 | spec: 20 | replicas: {{ if $service.scale }} {{ $service.scale }} {{ else }} {{ helmValue ".Values.deployment.defaultReplicas" }} {{ end }} 21 | selector: 22 | matchLabels: 23 | com.docker.compose.project: {{ $project }} 24 | com.docker.compose.service: {{$name}} 25 | strategy: 26 | type: {{ helmValue ".Values.deployment.strategy" }} 27 | template: 28 | metadata: 29 | labels: 30 | com.docker.compose.project: {{ $project }} 31 | com.docker.compose.service: {{ $name }} 32 | {{ range $name, $config := $service.networks}} 33 | com.docker.compose.network.{{ $name }}: "true" 34 | {{ end }} 35 | spec: 36 | {{ if $service.restart }} 37 | restartPolicy : {{ map $service.restart "always->Always" "on-failure->OnFailure" "no->Never"}} 38 | {{ end }} 39 | containers: 40 | - name: {{ if $service.container_name }}{{ $service.container_name | safe}}{{ else }}{{ $name | safe}}{{ end }} 41 | image: {{ helmValue ".Values.%s.image" $name }} 42 | imagePullPolicy: {{ helmValue ".Values.%s.imagePullPolicy" $name }} 43 | {{ if $service.entrypoint }} 44 | command: 45 | {{ range $service.entrypoint }} 46 | - {{ . }} 47 | {{ end }}{{ end }} 48 | {{ if $service.command }} 49 | args: 50 | {{ range $service.command }} 51 | - {{ . }} 52 | {{ end }}{{ end }} 53 | {{ if $service.working_dir }} 54 | workingDir: {{ $service.working_dir }}{{ end }} 55 | {{ if or $service.environment $service.models }} 56 | env: 57 | {{ range $key, $value := $service.environment }} 58 | - name: {{ $key }} 59 | value: {{ printf "%q" $value }}{{ end }} 60 | {{ range $key, $value := $service.models }} 61 | {{- if or (isString $value) (not $value) }} 62 | - name: {{ if isString $value }}{{ $value | uppercase }}{{ else }}{{ $key | uppercase }}{{ end }}_URL 63 | value: {{ helmValue "if .Values.modelRunner.enabled" }}"http://docker-model-runner/engines/v1/"{{ helmValue "else" }}{{ helmValue ".Values.modelRunner.hostEndpoint" }}{{ helmValue "end" }} 64 | - name: {{ if isString $value }}{{ $value | uppercase }}{{ else }}{{ $key | uppercase }}{{ end }}_MODEL 65 | value: "{{ if isString $value }}{{ or (index $.models $value).model $value }}{{ else }}{{ or (index $.models $key).model $key }}{{ end }}" 66 | {{- else }} 67 | - name: {{ if hasAttribute $value "endpoint_var" }}{{ getAttribute $value "endpoint_var" }}{{ else }}{{ $key | uppercase }}_URL{{ end }} 68 | value: {{ helmValue "if .Values.modelRunner.enabled" }}"http://docker-model-runner/engines/v1/"{{ helmValue "else" }}{{ helmValue ".Values.modelRunner.hostEndpoint" }}{{ helmValue "end" }} 69 | - name: {{ if hasAttribute $value "model_var" }}{{ getAttribute $value "model_var" }}{{ else }}{{ $key | uppercase }}_MODEL{{ end }} 70 | value: "{{ or (index $.models $key).model $key }}" 71 | {{- end }}{{ end }} 72 | {{ end }} 73 | 74 | {{ if or $service.user $service.group_add $service.sysctls $service.read_only $service.privileged $service.cap_add $service.cap_drop }} 75 | securityContext: 76 | {{ if $service.user }} 77 | runAsUser: {{ $service.user }} {{ end }} 78 | {{ if $service.group_add }} 79 | supplementalGroups: {{ $service.group_add }} {{ end }} 80 | {{ if $service.sysctls }} 81 | sysctls: 82 | {{ range $name, $value := $service.sysctls }} 83 | name: {{ $name }} 84 | value: {{ $value }} 85 | {{ end }} 86 | {{ end }} 87 | 88 | {{ if $service.read_only }} 89 | readOnlyRootFilesystem: {{ $service.read_only }} {{ end }} 90 | {{ if $service.privileged }} 91 | privileged: {{ $service.privileged }} {{ end }} 92 | {{ if or $service.cap_add $service.cap_drop }} 93 | capabilities: 94 | {{ if $service.cap_add }} 95 | add: {{ $service.cap_add }} {{ end }} 96 | {{ if $service.cap_drop }} 97 | drop: {{ $service.cap_drop }} {{ end }} 98 | {{ end }} 99 | {{ end }} 100 | 101 | resources: 102 | limits: 103 | {{ if $service.cpus }} 104 | cpu: {{ $service.cpus }} {{ else }} 105 | cpu: {{ helmValue ".Values.resources.defaultCpuLimit" }} {{ end }} 106 | {{ if $service.memory }} 107 | memory: {{ $service.memory }} {{ else }} 108 | memory: {{ helmValue ".Values.resources.defaultMemoryLimit" }} {{ end }} 109 | 110 | {{ if $service.healthcheck }} 111 | livenessProbe: 112 | exec: 113 | {{ if eq "CMD-SHELL" (index $service.healthcheck.test 0)}} 114 | command: 115 | - /bin/sh 116 | - -c 117 | - {{ $service.healthcheck.test | truncate 1 | join " "}} 118 | {{ else }} 119 | command: {{ $service.healthcheck.test | truncate 1 }} 120 | {{ end }} 121 | {{ if $service.healthcheck.interval }} 122 | periodSeconds: {{ $service.healthcheck.interval | seconds }}{{ end }} 123 | {{ if $service.healthcheck.start_period }} 124 | initialDelaySeconds: {{ $service.healthcheck.start_period | seconds }}{{ end }} 125 | {{ if $service.healthcheck.timeout }} 126 | timeoutSeconds: {{ $service.healthcheck.timeout | seconds }}{{ end }} 127 | {{ if $service.healthcheck.retries }} 128 | failureThreshold: {{ $service.healthcheck.retries }}{{ end }} 129 | {{ end }} 130 | 131 | 132 | {{ if $service.expose }} 133 | ports: 134 | {{ range $port := $service.expose }} 135 | - name: {{ portName $name $port }} 136 | containerPort: {{ $port }} 137 | {{ end }} 138 | {{ end }} 139 | 140 | {{ if or $service.volumes $service.secrets $service.configs }} 141 | volumeMounts: 142 | {{ range $volume := $service.volumes }} 143 | - name: {{ $volume.target | safe }} 144 | mountPath: {{ $volume.target }} 145 | {{ if $volume.read_only }} 146 | readOnly: true{{ end }} 147 | {{ end }} 148 | {{ range $secret := $service.secrets }} 149 | - name: {{ $secret.target | safe }} 150 | mountPath: {{ $secret.target }} 151 | subPath: {{ $secret.source }} 152 | readOnly: true 153 | {{ end }} 154 | {{ range $config := $service.configs }} 155 | - name: {{ $config.target | safe }} 156 | mountPath: {{ $config.target }} 157 | subPath: {{ $config.source }} 158 | readOnly: true 159 | {{ end }} 160 | {{ end }} 161 | 162 | 163 | {{ if or $service.dns $service.dns_search $service.dns_opt }} 164 | dnsConfig: 165 | {{ if $service.dns }} 166 | nameservers: 167 | {{ range $service.dns }} 168 | - {{ . }} 169 | {{ end }} 170 | {{ end }} 171 | {{ if $service.dns_search }} 172 | searches: 173 | {{ range $service.dns_search }} 174 | - {{ . }} 175 | {{ end }} 176 | {{ end }} 177 | {{ if $service.dns_opt }} 178 | options: 179 | {{ range $service.dns_opt }} 180 | - name: {{ . }} 181 | {{ end }} 182 | {{ end }} 183 | {{ end }} 184 | 185 | 186 | {{ if or $service.volumes $service.secrets $service.configs }} 187 | volumes: 188 | {{ range $secret := $service.secrets }} 189 | - name: {{ $secret.target | safe }} 190 | secret: 191 | secretName: {{ $secret.source | safe }} 192 | items: 193 | - key: {{ $secret.source }} 194 | path: {{ $secret.source }} 195 | {{ end }} 196 | 197 | {{ range $config := $service.configs }} 198 | - name: {{ $config.target | safe }} 199 | configMap: 200 | name: {{ helmValue ".Values.projectName"}} 201 | items: 202 | - key: {{ $config.source }} 203 | path: {{ $config.source }} 204 | {{ end }} 205 | 206 | {{ range $volume := $service.volumes }} 207 | - name: {{ $volume.target | safe }} 208 | {{ if eq $volume.type "volume" }} 209 | persistentVolumeClaim: 210 | claimName: {{ $name | safe }}-{{ $volume.source | safe }} 211 | {{ else if eq $volume.type "bind" }} 212 | hostPath: 213 | path: {{ $volume.source }} 214 | {{ else if eq $volume.type "tmpfs" }} 215 | emptyDir: 216 | {{ if $volume.tmpfs.size }} 217 | sizeLimit: {{ $volume.tmpfs.size }}{{ end }} 218 | {{ end }} 219 | 220 | {{ if $volume.read_only }} 221 | readOnly: true{{ end }} 222 | 223 | {{ end }} 224 | {{ end }} 225 | {{ end }} 226 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. --------------------------------------------------------------------------------