diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5ae04d665b08c..c767cc908032e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -275,12 +275,19 @@ jobs: - name: ls artifacts run: ls artifacts + - name: Publish Helm + run: | + set -euxo pipefail + ./scripts/helm.sh --push + mv ./dist/*.tgz ./artifacts/ + - name: Publish Release run: | ./scripts/publish_release.sh \ ${{ (github.event.inputs.dry_run || github.event.inputs.snapshot) && '--dry-run' }} \ ./artifacts/*.zip \ ./artifacts/*.tar.gz \ + ./artifacts/*.tgz \ ./artifacts/*.apk \ ./artifacts/*.deb \ ./artifacts/*.rpm diff --git a/Dockerfile b/Dockerfile index 489c7266485ca..6dcdcc21205bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,4 +14,9 @@ LABEL \ # The coder binary is injected by scripts/build_docker.sh. ADD coder /opt/coder +# Create coder group and user. +RUN addgroup -g 1000 coder && \ + adduser -D -g "" -h /home/coder -G coder -u 1000 -S -s /bin/sh coder +USER coder:coder + ENTRYPOINT [ "/opt/coder", "server" ] diff --git a/helm/.helmignore b/helm/.helmignore new file mode 100644 index 0000000000000..0e8a0eb36f4ca --- /dev/null +++ b/helm/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/Chart.yaml b/helm/Chart.yaml new file mode 100644 index 0000000000000..2b73b7c6d641c --- /dev/null +++ b/helm/Chart.yaml @@ -0,0 +1,25 @@ +apiVersion: v2 +name: coder +description: Remote development environments on your infrastructure +home: https://github.com/coder/coder + +# version and appVersion are injected at release and will always be shown as +# 0.1.0 in the repository. +type: application +version: "0.1.0" +appVersion: "0.1.0" + +# Coder has a hard requirement on Kubernetes 1.19, as this version introduced +# the networking.k8s.io/v1 API. +kubeVersion: ">= 1.19.0-0" + +keywords: + - coder + - terraform +sources: + - https://github.com/coder/coder/tree/main/helm +icon: https://helm.coder.com/coder_logo_black.png +maintainers: + - name: Coder Technologies, Inc. + email: support@coder.com + url: https://coder.com/contact diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl new file mode 100644 index 0000000000000..e723c6f1e9197 --- /dev/null +++ b/helm/templates/_helpers.tpl @@ -0,0 +1,33 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "coder.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "coder.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "coder.selectorLabels" -}} +app.kubernetes.io/name: {{ include "coder.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "coder.labels" -}} +helm.sh/chart: {{ include "coder.chart" . }} +{{ include "coder.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml new file mode 100644 index 0000000000000..cc4a66839e3ad --- /dev/null +++ b/helm/templates/deployment.yaml @@ -0,0 +1,62 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: coder + labels: + {{- include "coder.labels" . | nindent 4 }} +spec: + # NOTE: this is currently not used as coder v2 does not support high + # availability yet. + # replicas: {{ .Values.coder.replicaCount }} + replicas: 1 + selector: + matchLabels: + {{- include "coder.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "coder.selectorLabels" . | nindent 8 }} + spec: + restartPolicy: Always + terminationGracePeriodSeconds: 60 + containers: + - name: coder + image: "{{ .Values.coder.image.repo }}:{{ .Values.coder.image.tag | default (printf "v%v" .Chart.AppVersion) }}" + imagePullPolicy: {{ .Values.coder.image.pullPolicy }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + env: + {{- if .Values.coder.tls.secretName }} + - name: CODER_ADDRESS + value: "0.0.0.0:8443" + - name: CODER_TLS_ENABLE + value: "true" + - name: CODER_TLS_CERT_FILE + value: /etc/ssl/certs/coder/tls.crt + - name: CODER_TLS_KEY_FILE + value: /etc/ssl/certs/coder/tls.key + {{- else }} + - name: CODER_ADDRESS + value: "0.0.0.0:8080" + {{- end }} + {{- with .Values.coder.env -}} + {{ toYaml . | nindent 12 }} + {{- end }} + ports: + {{- if .Values.coder.tls.secretName }} + - name: https + containerPort: 8443 + protocol: TCP + {{- else }} + - name: http + containerPort: 8080 + protocol: TCP + {{- end }} + readinessProbe: + httpGet: + path: /api/v2/buildinfo + port: http + livenessProbe: + httpGet: + path: /api/v2/buildinfo + port: http diff --git a/helm/templates/service.yaml b/helm/templates/service.yaml new file mode 100644 index 0000000000000..84c47d9107da9 --- /dev/null +++ b/helm/templates/service.yaml @@ -0,0 +1,25 @@ +{{- if .Values.coder.service.enable }} +--- +apiVersion: v1 +kind: Service +metadata: + name: coder + labels: + {{- include "coder.labels" . | nindent 4 }} +spec: + type: {{ .Values.coder.service.type }} + ports: + {{- if .Values.coder.tls.secretName }} + - name: https + port: 443 + targetPort: https + protocol: TCP + {{- else }} + - name: http + port: 80 + targetPort: http + protocol: TCP + {{- end }} + selector: + {{- include "coder.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/helm/values.yaml b/helm/values.yaml new file mode 100644 index 0000000000000..2090296dc467d --- /dev/null +++ b/helm/values.yaml @@ -0,0 +1,71 @@ +# coder -- Primary configuration for `coder server`. +coder: + # NOTE: this is currently not used as coder v2 does not support high + # availability yet. + # # coder.replicaCount -- The number of Kubernetes deployment replicas. + # replicaCount: 1 + + # coder.image -- The image to use for Coder. + image: + # coder.image.repo -- The repository of the image. + repo: "ghcr.io/coder/coder" + # coder.image.tag -- The tag of the image, defaults to {{.Chart.AppVersion}} + # if not set. + tag: "" + # coder.image.pullPolicy -- The pull policy to use for the image. See: + # https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy + pullPolicy: IfNotPresent + + # coder.env -- The environment variables to set for Coder. These can be used + # to configure all aspects of `coder server`. Please see `coder server --help` + # for information about what environment variables can be set. + # + # Note: The following environment variables are set by default and cannot be + # overridden: + # - CODER_ADDRESS: set to 0.0.0.0:80 and cannot be changed. + # - CODER_TLS_ENABLE: set if tls.secretName is not empty. + # - CODER_TLS_CERT_FILE: set if tls.secretName is not empty. + # - CODER_TLS_KEY_FILE: set if tls.secretName is not empty. + env: + - name: CODER_ACCESS_URL + value: "https://coder.example.com" + #- name: CODER_PG_CONNECTION_URL + # value: "postgres://coder:password@postgres:5432/coder?sslmode=disable" + + # coder.tls -- The TLS configuration for Coder. + tls: + # coder.tls.secretName -- The name of the secret containing the TLS + # certificate. The secret should exist in the same namespace as the Helm + # deployment and should be of type "kubernetes.io/tls". The secret will be + # automatically mounted into the pod if specified, and the correct + # "CODER_TLS_*" environment variables will be set for you. + secretName: "" + + # coder.resources -- The resources to request for Coder. These are optional + # and are not set by default. + resources: {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + # coder.service -- The Service object to expose for Coder. + service: + # coder.service.enable -- Whether to create the Service object. + enable: true + # coder.service.type -- The type of service to expose. See: + # https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: LoadBalancer + # coder.service.externalTrafficPolicy -- The external traffic policy to use. + # You may need to change this to "Local" to preserve the source IP address + # in some situations. + # https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip + externalTrafficPolicy: Cluster + # coder.service.loadBalancerIP -- The IP address of the LoadBalancer. If not + # specified, a new IP will be generated each time the load balancer is + # recreated. It is recommended to manually create a static IP address in + # your cloud and specify it here in production to avoid accidental IP + # address changes. + loadBalancerIP: "" diff --git a/scripts/helm.sh b/scripts/helm.sh new file mode 100755 index 0000000000000..5978a5f373937 --- /dev/null +++ b/scripts/helm.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash + +# This script creates a Helm package for the given version. It will output a +# .tgz file at the specified path, and may optionally push it to the Coder OSS +# repo. +# +# ./helm.sh [--version 1.2.3] [--output path/to/coder.tgz] [--push] +# +# If no version is specified, defaults to the version from ./version.sh. +# +# If no output path is specified, defaults to +# "$repo_root/dist/coder_helm_$version.tgz". +# +# If the --push parameter is specified, the resulting artifact will be published +# to the Coder OSS repo. This requires `gsutil` to be installed and configured. + +set -euo pipefail +# shellcheck source=scripts/lib.sh +source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" + +version="" +output_path="" +push=0 + +args="$(getopt -o "" -l version:,output:,push -- "$@")" +eval set -- "$args" +while true; do + case "$1" in + --version) + version="$2" + shift 2 + ;; + --output) + output_path="$(realpath "$2")" + shift 2 + ;; + --push) + push="1" + shift + ;; + --) + shift + break + ;; + *) + error "Unrecognized option: $1" + ;; + esac +done + +# Remove the "v" prefix. +version="${version#v}" +if [[ "$version" == "" ]]; then + version="$(execrelative ./version.sh)" +fi + +if [[ "$output_path" == "" ]]; then + cdroot + mkdir -p dist + output_path="$(realpath "dist/coder_helm_$version.tgz")" +fi + +# Check dependencies +dependencies helm + +# Make a destination temporary directory, as you cannot fully control the output +# path of `helm package` except for the directory name :/ +cdroot +temp_dir="$(mktemp -d)" + +cdroot +cd ./helm +log "--- Packaging helm chart for version $version ($output_path)" +helm package \ + --version "$version" \ + --app-version "$version" \ + --destination "$temp_dir" \ + . 1>&2 + +log "Moving helm chart to $output_path" +cp "$temp_dir"/*.tgz "$output_path" +rm -rf "$temp_dir" + +if [[ "$push" == 1 ]]; then + log "--- Publishing helm chart..." + # TODO: figure out how/where we want to publish the helm chart +fi diff --git a/scripts/version.sh b/scripts/version.sh index 220da35328a27..628fdef3e4ba0 100755 --- a/scripts/version.sh +++ b/scripts/version.sh @@ -15,6 +15,11 @@ set -euo pipefail source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" cdroot +if [[ "${CODER_FORCE_VERSION:-}" != "" ]]; then + echo "$CODER_FORCE_VERSION" + exit 0 +fi + last_tag="$(git describe --tags --abbrev=0)" version="$last_tag"