1use gh_workflow::{Container, Event, Port, Push, Run, Step, Use, Workflow};
2use indoc::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(
119 r#"doctl kubernetes cluster kubeconfig save --expiry-seconds 600 "$CLUSTER_NAME""#,
120 )
121 .add_env(("CLUSTER_NAME", vars::CLUSTER_NAME))
122 }
123
124 fn start_rollout() -> Step<Run> {
125 named::bash(indoc! {r#"
126 set -eu
127 if [[ $GITHUB_REF_NAME = "collab-production" ]]; then
128 export ZED_KUBE_NAMESPACE=production
129 export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=10
130 export ZED_API_LOAD_BALANCER_SIZE_UNIT=2
131 elif [[ $GITHUB_REF_NAME = "collab-staging" ]]; then
132 export ZED_KUBE_NAMESPACE=staging
133 export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=1
134 export ZED_API_LOAD_BALANCER_SIZE_UNIT=1
135 else
136 echo "cowardly refusing to deploy from an unknown branch"
137 exit 1
138 fi
139
140 echo "Deploying collab:$GITHUB_SHA to $ZED_KUBE_NAMESPACE"
141
142 source script/lib/deploy-helpers.sh
143 export_vars_for_environment "$ZED_KUBE_NAMESPACE"
144
145 ZED_DO_CERTIFICATE_ID="$(doctl compute certificate list --format ID --no-header)"
146 export ZED_DO_CERTIFICATE_ID
147 export ZED_IMAGE_ID="registry.digitalocean.com/zed/collab:${GITHUB_SHA}"
148
149 export ZED_SERVICE_NAME=collab
150 export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT
151 export DATABASE_MAX_CONNECTIONS=850
152 envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
153 kubectl -n "$ZED_KUBE_NAMESPACE" rollout status "deployment/$ZED_SERVICE_NAME" --watch
154 echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"
155
156 export ZED_SERVICE_NAME=api
157 export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_API_LOAD_BALANCER_SIZE_UNIT
158 export DATABASE_MAX_CONNECTIONS=60
159 envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
160 kubectl -n "$ZED_KUBE_NAMESPACE" rollout status "deployment/$ZED_SERVICE_NAME" --watch
161 echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"
162 "#})
163 }
164
165 named::job(
166 dependant_job(deps)
167 .name("Deploy new server image")
168 .runs_on(runners::LINUX_XL)
169 .add_step(steps::checkout_repo())
170 .add_step(install_doctl())
171 .add_step(sign_into_kubernetes())
172 .add_step(start_rollout()),
173 )
174}