deploy_collab.rs

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