Publish collab docker images on CI, deploy pre-built images

Max Brunsfeld created

Change summary

.github/workflows/publish_collab_image.yml | 46 ++++++++++++++++++++++++
crates/collab/src/main.rs                  | 14 +++++--
script/deploy                              | 37 ++++++++++--------
3 files changed, 77 insertions(+), 20 deletions(-)

Detailed changes

.github/workflows/publish_collab_image.yml 🔗

@@ -0,0 +1,46 @@
+name: Publish Collab Server Image
+
+on:
+  push:
+    tags:
+      - collab-v*
+
+env:
+  DOCKER_BUILDKIT: 1
+  DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
+
+jobs:
+  publish:
+    name: Publish collab server image 
+    runs-on:
+      - self-hosted
+      - deploy
+    steps:
+      - name: Add Rust to the PATH
+        run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
+
+      - name: Sign into DigitalOcean docker registry
+        run: doctl registry login
+
+      - name: Checkout repo
+        uses: actions/checkout@v3
+        with:
+          clean: false
+
+      - name: Check that tag version matches package version
+        run: |
+          set -eu
+          package_version=$(cargo metadata --no-deps --format-version=1 | jq --raw-output '.packages[] | select(.name == "collab") | .version')
+          tag_version=$(echo $GITHUB_REF_NAME | sed -e 's/collab-v//')
+          if [[ $tag_version != $package_version ]]; then
+            echo "collab package version $package_version does not match git tag version $tag_version"
+            exit 1
+          fi
+          echo "Publishing image version: $package_version"
+          echo "COLLAB_VERSION=$package_version" >> $GITHUB_ENV
+
+      - name: Build docker image
+        run: docker build . --tag registry.digitalocean.com/zed/collab:v${COLLAB_VERSION}
+    
+      - name: Publish docker image
+        run: docker push registry.digitalocean.com/zed/collab:v${COLLAB_VERSION}

crates/collab/src/main.rs 🔗

@@ -9,7 +9,7 @@ mod db_tests;
 #[cfg(test)]
 mod integration_tests;
 
-use axum::{body::Body, Router};
+use axum::{routing::get, Router};
 use collab::{Error, Result};
 use db::{Db, PostgresDb};
 use serde::Deserialize;
@@ -22,6 +22,8 @@ use tracing_log::LogTracer;
 use tracing_subscriber::{filter::EnvFilter, fmt::format::JsonFields, Layer};
 use util::ResultExt;
 
+const VERSION: &'static str = env!("CARGO_PKG_VERSION");
+
 #[derive(Default, Deserialize)]
 pub struct Config {
     pub http_port: u16,
@@ -67,9 +69,9 @@ async fn main() -> Result<()> {
 
     rpc_server.start_recording_project_activity(Duration::from_secs(5 * 60), rpc::RealExecutor);
 
-    let app = Router::<Body>::new()
-        .merge(api::routes(&rpc_server, state.clone()))
-        .merge(rpc::routes(rpc_server));
+    let app = api::routes(&rpc_server, state.clone())
+        .merge(rpc::routes(rpc_server))
+        .merge(Router::new().route("/", get(handle_root)));
 
     axum::Server::from_tcp(listener)?
         .serve(app.into_make_service_with_connect_info::<SocketAddr>())
@@ -78,6 +80,10 @@ async fn main() -> Result<()> {
     Ok(())
 }
 
+async fn handle_root() -> String {
+    format!("collab v{VERSION}")
+}
+
 pub fn init_tracing(config: &Config) -> Option<()> {
     use std::str::FromStr;
     use tracing_subscriber::layer::SubscriberExt;

script/deploy 🔗

@@ -2,36 +2,41 @@
 
 # Prerequisites:
 #
-# - Log in to the DigitalOcean docker registry
-#   doctl registry login
-#
-# - Target the `zed-1` kubernetes cluster
-#   doctl kubernetes cluster kubeconfig save zed-1
+# - Log in to the DigitalOcean API, either interactively, by running
+#   `doctl auth init`, or by setting the `DIGITALOCEAN_ACCESS_TOKEN`
+#   environment variable.
 
 set -eu
 
-if [[ $# < 1 ]]; then
-  echo "Usage: $0 [production|staging|...]"
+if [[ $# < 2 ]]; then
+  echo "Usage: $0 <production|staging|preview> <tag-name>"
   exit 1
 fi
-
 export ZED_KUBE_NAMESPACE=$1
+COLLAB_VERSION=$2
+
 ENV_FILE="crates/collab/k8s/environments/${ZED_KUBE_NAMESPACE}.sh"
 if [[ ! -f $ENV_FILE ]]; then
   echo "Invalid environment name '${ZED_KUBE_NAMESPACE}'"
   exit 1
 fi
+export $(cat $ENV_FILE)
 
-if [[ $ZED_KUBE_NAMESPACE == "production" && -n $(git status --short) ]]; then
-  echo "Cannot deploy uncommited changes to production"
+if [[ ! $COLLAB_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+  echo "Invalid version number '$COLLAB_VERSION'"
   exit 1
 fi
+TAG_NAMES=$(doctl registry repository list-tags collab --no-header --format Tag)
+if ! $(echo "${TAG_NAMES}" | grep -Fqx v${COLLAB_VERSION}); then
+  echo "No such image tag: 'zed/collab:v${COLLAB_VERSION}'"
+  echo "Found tags"
+  echo "${TAG_NAMES}"
+  exit 1
+fi
+export ZED_IMAGE_ID="registry.digitalocean.com/zed/collab:v${COLLAB_VERSION}"
 
-git_sha=$(git rev-parse HEAD)
-export ZED_IMAGE_ID="registry.digitalocean.com/zed/collab:${ZED_KUBE_NAMESPACE}-${git_sha}"
-export $(cat $ENV_FILE)
-
-docker build . --tag "$ZED_IMAGE_ID"
-docker push "$ZED_IMAGE_ID"
+if [[ $(kubectl config current-context 2> /dev/null) != do-nyc1-zed-1 ]]; then
+  doctl kubernetes cluster kubeconfig save zed-1
+fi
 
 envsubst < crates/collab/k8s/manifest.template.yml | kubectl apply -f -