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}