diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7ec61c1b..c4c2a2b3 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -24,6 +24,7 @@ jobs: runs-on: ubuntu-latest outputs: meta-images: ${{ steps.meta-images.outputs.value }} + image-tag: ${{ steps.meta-images.outputs.image-tag }} steps: - name: Build image metadata inputs id: meta-images @@ -33,6 +34,7 @@ jobs: DOCKERHUB_REPOSITORY: ${{ vars.DOCKERHUB_REPOSITORY || secrets.DOCKERHUB_REPOSITORY }} DEFAULT_REPOSITORY_NAME: ${{ github.event.repository.name }} run: | + image_tag="sha-${GITHUB_SHA::7}" if [ -n "$DOCKERHUB_REPOSITORY" ]; then case "$DOCKERHUB_REPOSITORY" in */*) @@ -46,6 +48,7 @@ jobs: dockerhub_repository="$DOCKERHUB_USERNAME/$DEFAULT_REPOSITORY_NAME" fi { + echo "image-tag=$image_tag" echo 'value<&2 + if [ -n "$RELEASE_TAG" ] && [ -n "$CHANNEL" ]; then + echo "release-tag and channel are mutually exclusive." >&2 exit 1 fi - if [ "$app_version" != "$tag_version" ]; then - echo "Chart appVersion ($app_version) must match release tag ($tag_version)." >&2 + if [ -z "$RELEASE_TAG" ] && [ -z "$CHANNEL" ]; then + echo "Either release-tag or channel is required." >&2 exit 1 fi - if [ "$cargo_version" != "$tag_version" ]; then - echo "Cargo workspace version ($cargo_version) must match release tag ($tag_version)." >&2 + + if [ "$chart_version" != "$cargo_version" ] || [ "$app_version" != "$cargo_version" ]; then + echo "Source chart version ($chart_version), appVersion ($app_version), and Cargo workspace version ($cargo_version) must match." >&2 exit 1 fi + if [ -n "$RELEASE_TAG" ]; then + tag_version="${RELEASE_TAG#v}" + if [ "$chart_version" != "$tag_version" ]; then + echo "Chart version ($chart_version) must match release tag ($tag_version)." >&2 + exit 1 + fi + publish_version="$chart_version" + publish_app_version="$app_version" + publish_image_tag="${IMAGE_TAG:-$tag_version}" + else + if ! [[ "$CHANNEL" =~ ^[0-9A-Za-z-]+$ ]]; then + echo "Development channel must contain only letters, digits, and hyphens." >&2 + exit 1 + fi + if ! [[ "$IMAGE_TAG" =~ ^[0-9A-Za-z_][0-9A-Za-z._-]{0,127}$ ]]; then + echo "Development image-tag must be a valid OCI image tag." >&2 + exit 1 + fi + + short_sha="${GITHUB_SHA::7}" + version_suffix="$CHANNEL.${GITHUB_RUN_NUMBER}.${GITHUB_RUN_ATTEMPT}.g$short_sha" + if [[ "$chart_version" == *-* ]]; then + publish_version="$chart_version.$version_suffix" + else + publish_version="$chart_version-$version_suffix" + fi + publish_app_version="$IMAGE_TAG" + publish_image_tag="$IMAGE_TAG" + fi + repo="$(printf '%s' "${GITHUB_REPOSITORY#*/}" | tr '[:upper:]' '[:lower:]')" if [ -n "$DOCKERHUB_REPOSITORY" ]; then @@ -96,8 +142,9 @@ jobs: { echo "name=$chart_name" - echo "version=$chart_version" - echo "app_version=$app_version" + echo "version=$publish_version" + echo "app_version=$publish_app_version" + echo "image_tag=$publish_image_tag" echo "dockerhub_username=$DOCKERHUB_USERNAME" echo 'oci_repos<> "$GITHUB_OUTPUT" - - name: Set release image repository + - name: Set published chart and image metadata env: + PUBLISH_CHART_VERSION: ${{ steps.chart.outputs.version }} + PUBLISH_APP_VERSION: ${{ steps.chart.outputs.app_version }} PUBLISH_IMAGE_REPOSITORY: ${{ github.repository }} + PUBLISH_IMAGE_TAG: ${{ steps.chart.outputs.image_tag }} run: | ruby <<'RUBY' require "yaml" - path = "helm/synctv/values.yaml" - values = YAML.load_file(path) + chart_path = "helm/synctv/Chart.yaml" + chart = YAML.load_file(chart_path) + chart["version"] = ENV.fetch("PUBLISH_CHART_VERSION") + chart["appVersion"] = ENV.fetch("PUBLISH_APP_VERSION") + File.write(chart_path, YAML.dump(chart)) + + values_path = "helm/synctv/values.yaml" + values = YAML.load_file(values_path) image = values.fetch("image") image["registry"] = "ghcr.io" image["repository"] = ENV.fetch("PUBLISH_IMAGE_REPOSITORY").downcase - File.write(path, YAML.dump(values)) - puts "OCI release image: #{image['registry']}/#{image['repository']}" + image["tag"] = ENV.fetch("PUBLISH_IMAGE_TAG") + File.write(values_path, YAML.dump(values)) + puts "OCI chart version: #{chart['version']}" + puts "OCI chart default image: #{image['registry']}/#{image['repository']}:#{image['tag']}" RUBY - - name: Validate chart - run: make validate-helm + - name: Validate published chart + env: + PUBLISH_IMAGE_REPOSITORY: ${{ github.repository }} + PUBLISH_IMAGE_TAG: ${{ steps.chart.outputs.image_tag }} + run: | + helm lint ./helm/synctv + rendered_image="$(helm template synctv ./helm/synctv --namespace synctv | ruby -ryaml -e ' + YAML.load_stream(STDIN.read).compact.each do |document| + next unless document["kind"] == "Deployment" + containers = document.dig("spec", "template", "spec", "containers") || [] + container = containers.find { |item| item["name"] == "synctv" } + if container + puts container.fetch("image") + exit + end + end + abort "SyncTV Deployment image was not rendered" + ')" + expected_image="ghcr.io/${PUBLISH_IMAGE_REPOSITORY,,}:$PUBLISH_IMAGE_TAG" + if [ "$rendered_image" != "$expected_image" ]; then + echo "Rendered image ($rendered_image) must match published image ($expected_image)." >&2 + exit 1 + fi - name: Package chart run: | diff --git a/helm/synctv/templates/_helpers.tpl b/helm/synctv/templates/_helpers.tpl index c74e2872..f5b40495 100644 --- a/helm/synctv/templates/_helpers.tpl +++ b/helm/synctv/templates/_helpers.tpl @@ -147,6 +147,13 @@ Return the gRPC service name {{- include "synctv.nameWithSuffix" (list (include "synctv.fullname" .) "grpc") }} {{- end }} +{{/* +Return the health service name +*/}} +{{- define "synctv.healthServiceName" -}} +{{- include "synctv.nameWithSuffix" (list (include "synctv.fullname" .) "health") }} +{{- end }} + {{/* Return the RTMP service name */}} diff --git a/helm/synctv/templates/service.yaml b/helm/synctv/templates/service.yaml index 320ce470..da2193f8 100644 --- a/helm/synctv/templates/service.yaml +++ b/helm/synctv/templates/service.yaml @@ -37,7 +37,7 @@ spec: apiVersion: v1 kind: Service metadata: - name: {{ include "synctv.fullname" . }}-health + name: {{ include "synctv.healthServiceName" . }} labels: {{- include "synctv.labels" . | nindent 4 }} app.kubernetes.io/component: health