deploy_collab.rs

  1use gh_workflow::{Container, Event, Port, Push, Run, Step, Use, Workflow};
  2use indoc::{formatdoc, indoc};
  3
  4use crate::tasks::workflows::{
  5    runners::{self, Platform},
  6    steps::{self, CommonJobConditions, NamedJob, dependant_job, named},
  7    vars,
  8};
  9
 10pub(crate) fn deploy_collab() -> Workflow {
 11    let style = style();
 12    let tests = tests(&[&style]);
 13    let publish = publish(&[&style, &tests]);
 14    let deploy = deploy(&[&publish]);
 15
 16    named::workflow()
 17        .on(Event::default().push(Push::default().add_tag("collab-production")))
 18        .add_env(("DOCKER_BUILDKIT", "1"))
 19        .add_job(style.name, style.job)
 20        .add_job(tests.name, tests.job)
 21        .add_job(publish.name, publish.job)
 22        .add_job(deploy.name, deploy.job)
 23}
 24
 25fn style() -> NamedJob {
 26    named::job(
 27        dependant_job(&[])
 28            .name("Check formatting and Clippy lints")
 29            .with_repository_owner_guard()
 30            .runs_on(runners::LINUX_XL)
 31            .add_step(steps::checkout_repo().add_with(("fetch-depth", 0)))
 32            .add_step(steps::setup_cargo_config(Platform::Linux))
 33            .add_step(steps::cache_rust_dependencies_namespace())
 34            .add_step(steps::cargo_fmt())
 35            .add_step(steps::clippy(Platform::Linux)),
 36    )
 37}
 38
 39fn tests(deps: &[&NamedJob]) -> NamedJob {
 40    fn run_collab_tests() -> Step<Run> {
 41        named::bash("cargo nextest run --package collab --no-fail-fast")
 42    }
 43
 44    named::job(
 45        dependant_job(deps)
 46            .name("Run tests")
 47            .runs_on(runners::LINUX_XL)
 48            .add_service(
 49                "postgres",
 50                Container::new("postgres:15")
 51                    .add_env(("POSTGRES_HOST_AUTH_METHOD", "trust"))
 52                    .ports(vec![Port::Name("5432:5432".into())])
 53                    .options(
 54                        "--health-cmd pg_isready \
 55                         --health-interval 500ms \
 56                         --health-timeout 5s \
 57                         --health-retries 10",
 58                    ),
 59            )
 60            .add_step(steps::checkout_repo().add_with(("fetch-depth", 0)))
 61            .add_step(steps::setup_cargo_config(Platform::Linux))
 62            .add_step(steps::cache_rust_dependencies_namespace())
 63            .add_step(steps::cargo_install_nextest())
 64            .add_step(steps::clear_target_dir_if_large(Platform::Linux))
 65            .add_step(run_collab_tests()),
 66    )
 67}
 68
 69fn publish(deps: &[&NamedJob]) -> NamedJob {
 70    fn install_doctl() -> Step<Use> {
 71        named::uses("digitalocean", "action-doctl", "v2")
 72            .add_with(("token", vars::DIGITALOCEAN_ACCESS_TOKEN))
 73    }
 74
 75    fn sign_into_registry() -> Step<Run> {
 76        named::bash("doctl registry login")
 77    }
 78
 79    fn build_docker_image() -> Step<Run> {
 80        named::bash(indoc! {r#"
 81            docker build -f Dockerfile-collab \
 82              --build-arg "GITHUB_SHA=$GITHUB_SHA" \
 83              --tag "registry.digitalocean.com/zed/collab:$GITHUB_SHA" \
 84              .
 85        "#})
 86    }
 87
 88    fn publish_docker_image() -> Step<Run> {
 89        named::bash(r#"docker push "registry.digitalocean.com/zed/collab:${GITHUB_SHA}""#)
 90    }
 91
 92    fn prune_docker_system() -> Step<Run> {
 93        named::bash("docker system prune --filter 'until=72h' -f")
 94    }
 95
 96    named::job(
 97        dependant_job(deps)
 98            .name("Publish collab server image")
 99            .runs_on(runners::LINUX_XL)
100            .add_step(install_doctl())
101            .add_step(sign_into_registry())
102            .add_step(steps::checkout_repo())
103            .add_step(build_docker_image())
104            .add_step(publish_docker_image())
105            .add_step(prune_docker_system()),
106    )
107}
108
109fn deploy(deps: &[&NamedJob]) -> NamedJob {
110    fn install_doctl() -> Step<Use> {
111        named::uses("digitalocean", "action-doctl", "v2")
112            .add_with(("token", vars::DIGITALOCEAN_ACCESS_TOKEN))
113    }
114
115    fn sign_into_kubernetes() -> Step<Run> {
116        named::bash(formatdoc! {r#"
117            doctl kubernetes cluster kubeconfig save --expiry-seconds 600 {cluster_name}
118        "#, cluster_name = vars::CLUSTER_NAME})
119    }
120
121    fn start_rollout() -> Step<Run> {
122        named::bash(indoc! {r#"
123            set -eu
124            if [[ $GITHUB_REF_NAME = "collab-production" ]]; then
125              export ZED_KUBE_NAMESPACE=production
126              export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=10
127              export ZED_API_LOAD_BALANCER_SIZE_UNIT=2
128            elif [[ $GITHUB_REF_NAME = "collab-staging" ]]; then
129              export ZED_KUBE_NAMESPACE=staging
130              export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=1
131              export ZED_API_LOAD_BALANCER_SIZE_UNIT=1
132            else
133              echo "cowardly refusing to deploy from an unknown branch"
134              exit 1
135            fi
136
137            echo "Deploying collab:$GITHUB_SHA to $ZED_KUBE_NAMESPACE"
138
139            source script/lib/deploy-helpers.sh
140            export_vars_for_environment $ZED_KUBE_NAMESPACE
141
142            ZED_DO_CERTIFICATE_ID="$(doctl compute certificate list --format ID --no-header)"
143            export ZED_DO_CERTIFICATE_ID
144            export ZED_IMAGE_ID="registry.digitalocean.com/zed/collab:${GITHUB_SHA}"
145
146            export ZED_SERVICE_NAME=collab
147            export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT
148            export DATABASE_MAX_CONNECTIONS=850
149            envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
150            kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch
151            echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"
152
153            export ZED_SERVICE_NAME=api
154            export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_API_LOAD_BALANCER_SIZE_UNIT
155            export DATABASE_MAX_CONNECTIONS=60
156            envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
157            kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch
158            echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"
159        "#})
160    }
161
162    named::job(
163        dependant_job(deps)
164            .name("Deploy new server image")
165            .runs_on(runners::LINUX_XL)
166            .add_step(steps::checkout_repo())
167            .add_step(install_doctl())
168            .add_step(sign_into_kubernetes())
169            .add_step(start_rollout()),
170    )
171}