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}