From fe2f2b52af39d8e0e9f75016cb1e77c01ed7db9d Mon Sep 17 00:00:00 2001 From: Tim McGilchrist Date: Thu, 30 Apr 2026 09:25:43 +1000 Subject: [PATCH 1/5] Remove gitlab pipeline configuration. Delete the gitlab: service, the three GitLab secrets, the gitlab-data and gitlab-capnp-secrets volumes, the web service's --gitlab-backend flag, and its gitlab-capnp-secrets mount. --- stack.yml.in | 45 --------------------------------------------- 1 file changed, 45 deletions(-) diff --git a/stack.yml.in b/stack.yml.in index 2ee7b605..10604b1b 100644 --- a/stack.yml.in +++ b/stack.yml.in @@ -2,8 +2,6 @@ version: '3.1' volumes: data: capnp-secrets: - gitlab-data: - gitlab-capnp-secrets: secrets: ocaml-ci-github-key: @@ -14,12 +12,6 @@ secrets: external: true ocaml-ci-webhook-secret: external: true - ocaml-ci-gitlab-oauth: - external: true - ocaml-ci-gitlab-token: - external: true - ocaml-ci-gitlab-webhook-secret: - external: true ocaml-ci-solver-cap: external: true @@ -62,51 +54,14 @@ services: sysctls: - 'net.ipv4.tcp_keepalive_time=60' - gitlab: - image: ocurrent/ocaml-ci-gitlab-service:live - # image: ocaml-ci-gitlab-service - # For local deploys using docker -c ocaml.ci.dev build -t ocaml-ci-gitlab-service -f Dockerfile.gitlab . - command: > - --gitlab-oauth /run/secrets/ocaml-ci-gitlab-oauth - --gitlab-token-file /run/secrets/ocaml-ci-gitlab-token - --gitlab-webhook-secret-file /run/secrets/ocaml-ci-gitlab-webhook-secret - --submission-service /run/secrets/ocaml-ci-submission-cap - --submission-solver-service /run/secrets/ocaml-ci-solver-cap - --submission-query-service /run/secrets/ocaml-ci-submission-cap - --capnp-public-address=tcp:ocaml.ci.dev:8202 - --capnp-listen-address=tcp:0.0.0.0:9000 - --migration-path /migrations - --verbosity info - environment: - - "CAPNP_PROFILE=production" - - "PLATFORMS=all" - - "DOCKER_BUILDKIT=1" - - "PROGRESS_NO_TRUNC=1" - ports: - - '8202:9000' - volumes: - - 'gitlab-data:/var/lib/ocurrent' - - '/var/run/docker.sock:/var/run/docker.sock' - - 'gitlab-capnp-secrets:/capnp-secrets' - secrets: - - 'ocaml-ci-gitlab-oauth' - - 'ocaml-ci-gitlab-token' - - 'ocaml-ci-submission-cap' - - 'ocaml-ci-solver-cap' - - 'ocaml-ci-gitlab-webhook-secret' - sysctls: - - 'net.ipv4.tcp_keepalive_time=60' - web: image: ocurrent/ocaml-ci-web:live # image: ocaml-ci-web # For local deploys using docker -c ocaml.ci.dev build -t ocaml-ci-web -f Dockerfile.web . command: > --backend /capnp-secrets/ocaml-ci-admin.cap - --gitlab-backend /gitlab-capnp-secrets/ocaml-ci-gitlab-admin.cap --listen-prometheus=9090 volumes: - 'capnp-secrets:/capnp-secrets:ro' - - 'gitlab-capnp-secrets:/gitlab-capnp-secrets:ro' sysctls: - 'net.ipv4.tcp_keepalive_time=60' From 513a862ffcec38b3ce04a6e9973343b8aa0eb13d Mon Sep 17 00:00:00 2001 From: Tim McGilchrist Date: Thu, 30 Apr 2026 09:47:02 +1000 Subject: [PATCH 2/5] Remove gitlab web-ui --- Dockerfile.web | 3 --- bin/add-profile-picture | 20 ++++------------ deploy-data/gitlab-organisations.txt | 5 ---- web-ui/controller/api/gitlab.ml | 1 - web-ui/controller/api/gitlab.mli | 1 - web-ui/controller/gitlab.ml | 1 - web-ui/controller/gitlab.mli | 1 - web-ui/main.ml | 14 ++--------- web-ui/router.ml | 35 +++++----------------------- web-ui/router.mli | 5 +--- web-ui/view/api/git_forge.ml | 7 +++--- web-ui/view/api/gitlab.ml | 3 --- web-ui/view/api/gitlab.mli | 1 - web-ui/view/common.ml | 15 ------------ web-ui/view/gitlab.ml | 26 --------------------- web-ui/view/gitlab.mli | 1 - web-ui/view/organisation.ml | 1 - web-ui/view/ref.ml | 7 ------ web-ui/view/step.ml | 1 - 19 files changed, 16 insertions(+), 132 deletions(-) delete mode 100644 deploy-data/gitlab-organisations.txt delete mode 100644 web-ui/controller/api/gitlab.ml delete mode 100644 web-ui/controller/api/gitlab.mli delete mode 100644 web-ui/controller/gitlab.ml delete mode 100644 web-ui/controller/gitlab.mli delete mode 100644 web-ui/view/api/gitlab.ml delete mode 100644 web-ui/view/api/gitlab.mli delete mode 100644 web-ui/view/gitlab.ml delete mode 100644 web-ui/view/gitlab.mli diff --git a/Dockerfile.web b/Dockerfile.web index c38b6cc1..b7240807 100644 --- a/Dockerfile.web +++ b/Dockerfile.web @@ -41,9 +41,6 @@ COPY --from=build --link /src/_build/install/default/bin/ocaml-ci-web /usr/local ## Load profile-pictures of registered organisations RUN mkdir -p /profile-pictures/github -RUN mkdir -p /profile-pictures/gitlab COPY --from=build --link /src/bin/add-profile-picture /usr/local/bin COPY --from=build --link /src/deploy-data/github-organisations.txt /github-organisations.txt -COPY --from=build --link /src/deploy-data/gitlab-organisations.txt /gitlab-organisations.txt RUN xargs -n 1 /usr/local/bin/add-profile-picture --github < /github-organisations.txt -RUN xargs -n 1 /usr/local/bin/add-profile-picture --gitlab < /gitlab-organisations.txt diff --git a/bin/add-profile-picture b/bin/add-profile-picture index f65c8b47..ffe377d9 100755 --- a/bin/add-profile-picture +++ b/bin/add-profile-picture @@ -2,13 +2,11 @@ usage() { cat >&2 <&2 "Unknown flag: $1" usage @@ -40,15 +34,9 @@ if [ $# -ne 1 ]; then usage fi -PROFILE_PICTURE_URL="" ORGANISATION_NAME=$1 PROFILE_PICTURE_PATH="/profile-pictures/$GITFORGE/$ORGANISATION_NAME.png" - -if [ "$GITFORGE" = "github" ]; then - PROFILE_PICTURE_URL="https://github.com/$ORGANISATION_NAME.png?size=200" -else - PROFILE_PICTURE_URL="$(curl -X GET https://gitlab.com/api/v4/users\?username="$ORGANISATION_NAME" | jq '.[] | .avatar_url' | sed 's/"//g')" >/dev/null -fi +PROFILE_PICTURE_URL="https://github.com/$ORGANISATION_NAME.png?size=200" if [ "$PROFILE_PICTURE_URL" = "" ]; then echo "ERROR: Profile picture lookup for $ORGANISATION_NAME" diff --git a/deploy-data/gitlab-organisations.txt b/deploy-data/gitlab-organisations.txt deleted file mode 100644 index ae4c9df0..00000000 --- a/deploy-data/gitlab-organisations.txt +++ /dev/null @@ -1,5 +0,0 @@ -talex5 -tmcgilchrist -nomadic-labs -maiste -raphael-proust diff --git a/web-ui/controller/api/gitlab.ml b/web-ui/controller/api/gitlab.ml deleted file mode 100644 index 719e6bba..00000000 --- a/web-ui/controller/api/gitlab.ml +++ /dev/null @@ -1 +0,0 @@ -include Git_forge.Make (Api_view.Gitlab) diff --git a/web-ui/controller/api/gitlab.mli b/web-ui/controller/api/gitlab.mli deleted file mode 100644 index d9bea40c..00000000 --- a/web-ui/controller/api/gitlab.mli +++ /dev/null @@ -1 +0,0 @@ -include Git_forge.Api_controller diff --git a/web-ui/controller/gitlab.ml b/web-ui/controller/gitlab.ml deleted file mode 100644 index 96284a85..00000000 --- a/web-ui/controller/gitlab.ml +++ /dev/null @@ -1 +0,0 @@ -include Git_forge.Make (View.Gitlab) diff --git a/web-ui/controller/gitlab.mli b/web-ui/controller/gitlab.mli deleted file mode 100644 index 3b2abf63..00000000 --- a/web-ui/controller/gitlab.mli +++ /dev/null @@ -1 +0,0 @@ -include Git_forge.Controller diff --git a/web-ui/main.ml b/web-ui/main.ml index 5d5fcf41..2bafdde4 100644 --- a/web-ui/main.ml +++ b/web-ui/main.ml @@ -17,12 +17,10 @@ let setup_logs level = Logs.set_level level; Dream.initialize_log () -let main interface port github_pipeline_cap gitlab_pipeline_cap - prometheus_config log_level = +let main interface port github_pipeline_cap prometheus_config log_level = Lwt_main.run (let () = setup_logs log_level in let github = Option.map Backend.make github_pipeline_cap in - let gitlab = Option.map Backend.make gitlab_pipeline_cap in let web = Dream.serve ~interface ~port ~error_handler: @@ -31,7 +29,7 @@ let main interface port github_pipeline_cap gitlab_pipeline_cap @@ Middleware.no_trailing_slash @@ Dream.memory_sessions @@ Dream.flash - @@ Router.create ~github ~gitlab + @@ Router.create ~github in Lwt.choose (web :: Prometheus_unix.serve prometheus_config)) @@ -57,13 +55,6 @@ let backend_cap = ~doc:"The capability file giving access to the GitHub backend service." ~docv:"CAP" [ "backend" ] -let gitlab_backend_cap = - Arg.value - @@ Arg.opt (Arg.some Capnp_rpc_unix.sturdy_uri) None - @@ Arg.info - ~doc:"The capability file giving access to the GitLab backend service." - ~docv:"CAP" [ "gitlab-backend" ] - let cmd = let doc = "A web front-end for OCaml-CI" in let info = Cmd.info "ocaml-ci-web" ~doc in @@ -73,7 +64,6 @@ let cmd = $ interface $ port $ backend_cap - $ gitlab_backend_cap $ Prometheus_unix.opts $ Logs_cli.level ()) diff --git a/web-ui/router.ml b/web-ui/router.ml index 49a2d338..20154e30 100644 --- a/web-ui/router.ml +++ b/web-ui/router.ml @@ -256,37 +256,18 @@ let documentation = @@ Controller.Documentation.user_guide website_scheme_and_domain); ] -let root ~gitlab ~github = +let root ~github = [ Dream.get "/" (fun _ -> - match (github, gitlab) with - | None, None -> + match github with + | None -> Dream.log "No backend available"; Dream.empty `Internal_Server_Error - | Some github, None -> + | Some github -> let orgs = [ ("github", "GitHub", github) ] in - Controller.Index.list_orgs ~orgs - | None, Some gitlab -> - Controller.Index.list_orgs ~orgs:[ ("gitlab", "GitLab", gitlab) ] - | Some github, Some gitlab -> - let orgs = - [ ("github", "GitHub", github); ("gitlab", "GitLab", gitlab) ] - in Controller.Index.list_orgs ~orgs); ] -let build_gitlab_route gitlab = - let module Gitlab = Route (struct - let prefix = "gitlab" - let request = "merge-request" - let backend = gitlab - let extra_routes = [] - - module Api = Api_controller.Gitlab - module Controller = Controller.Gitlab - end) in - Gitlab.routes () - let build_github_route github = let module Github = Route (struct let prefix = "github" @@ -322,12 +303,8 @@ let build_github_route github = end) in Github.routes () -let create ~github ~gitlab = - let gitlab_route = - match gitlab with None -> [] | Some gitlab -> build_gitlab_route gitlab - in +let create ~github = let github_route = match github with None -> [] | Some github -> build_github_route github in - Dream.router - (root ~gitlab ~github @ static @ documentation @ gitlab_route @ github_route) + Dream.router (root ~github @ static @ documentation @ github_route) diff --git a/web-ui/router.mli b/web-ui/router.mli index bd0ddf74..382239e4 100644 --- a/web-ui/router.mli +++ b/web-ui/router.mli @@ -1,4 +1 @@ -val create : - github:Controller.Backend.t option -> - gitlab:Controller.Backend.t option -> - Dream.handler +val create : github:Controller.Backend.t option -> Dream.handler diff --git a/web-ui/view/api/git_forge.ml b/web-ui/view/api/git_forge.ml index b51bb62b..5db12d2c 100644 --- a/web-ui/view/api/git_forge.ml +++ b/web-ui/view/api/git_forge.ml @@ -20,10 +20,9 @@ end (* This module is starting out as very thin wrapper on a string. It's meant to help stay consistent with the design intent of implementing - different git-forges via a functorial approach. As we round out the - Gitlab work this module may grow. If the implementation between the different - git-forges turns out to be the same, this module will not grow and we may - choose to back away from this approach. *) + different git-forges via a functorial approach. If the implementation + between the different git-forges turns out to be the same, this module + will not grow and we may choose to back away from this approach. *) module type M_Git_forge = sig val prefix : string end diff --git a/web-ui/view/api/gitlab.ml b/web-ui/view/api/gitlab.ml deleted file mode 100644 index 2356f146..00000000 --- a/web-ui/view/api/gitlab.ml +++ /dev/null @@ -1,3 +0,0 @@ -include Git_forge.Make (struct - let prefix = "gitlab" -end) diff --git a/web-ui/view/api/gitlab.mli b/web-ui/view/api/gitlab.mli deleted file mode 100644 index 9d4d42de..00000000 --- a/web-ui/view/api/gitlab.mli +++ /dev/null @@ -1 +0,0 @@ -include Git_forge.Api diff --git a/web-ui/view/common.ml b/web-ui/view/common.ml index 2a9a26ec..41ca0950 100644 --- a/web-ui/view/common.ml +++ b/web-ui/view/common.ml @@ -335,21 +335,6 @@ let github_logo = []; ]) -let gitlab_logo = - Tyxml.Svg.( - Tyxml.Html.svg - ~a:[ a_class [ "w-4 h-4" ]; a_fill `None; a_viewBox (0., 0., 380., 380.) ] - [ - path - ~a: - [ - a_class [ "svg-fill" ]; - a_d - "M282.83,170.73l-.27-.69-26.14-68.22a6.81,6.81,0,0,0-2.69-3.24,7,7,0,0,0-8,.43,7,7,0,0,0-2.32,3.52l-17.65,54H154.29l-17.65-54A6.86,6.86,0,0,0,134.32,99a7,7,0,0,0-8-.43,6.87,6.87,0,0,0-2.69,3.24L97.44,170l-.26.69a48.54,48.54,0,0,0,16.1,56.1l.09.07.24.17,39.82,29.82,19.7,14.91,12,9.06a8.07,8.07,0,0,0,9.76,0l12-9.06,19.7-14.91,40.06-30,.1-.08A48.56,48.56,0,0,0,282.83,170.73Z"; - ] - []; - ]) - let information_icon = Tyxml.Svg.( Tyxml.Html.svg diff --git a/web-ui/view/gitlab.ml b/web-ui/view/gitlab.ml deleted file mode 100644 index dbe2d357..00000000 --- a/web-ui/view/gitlab.ml +++ /dev/null @@ -1,26 +0,0 @@ -include Git_forge.Make (struct - let prefix = "gitlab" - let request_abbrev = "MR" - let request_prefix = "merge-request" - let org_url ~org = Printf.sprintf "https://gitlab.com/%s" org - let repo_url ~org ~repo = Printf.sprintf "https://gitlab.com/%s/%s" org repo - - let commit_url ~org ~repo ~hash = - Printf.sprintf "https://gitlab.com/%s/%s/-/commit/%s" org repo hash - - let branch_url ~org ~repo ref = - Fmt.str "https://gitlab.com/%s/%s/-/tree/%s" org repo ref - - let request_url ~org ~repo id = - Fmt.str "https://gitlab.com/%s/%s/-/merge_requests/%s" org repo id - - let parse_ref r = - match Astring.String.cuts ~sep:"/" r with - | "refs" :: "heads" :: branch -> - let branch = Astring.String.concat ~sep:"/" branch in - `Branch branch - | [ "refs"; "merge-requests"; id; "head" ] -> - let id = int_of_string id in - `Request id - | _ -> `Unknown r -end) diff --git a/web-ui/view/gitlab.mli b/web-ui/view/gitlab.mli deleted file mode 100644 index 74feb354..00000000 --- a/web-ui/view/gitlab.mli +++ /dev/null @@ -1 +0,0 @@ -include Git_forge.View diff --git a/web-ui/view/organisation.ml b/web-ui/view/organisation.ml index 66faa81a..6caeb5fa 100644 --- a/web-ui/view/organisation.ml +++ b/web-ui/view/organisation.ml @@ -27,7 +27,6 @@ module Make (M : Forge_prefix) = struct let logo = match M.prefix with | "github" -> Common.github_logo - | "gitlab" -> Common.gitlab_logo | _ -> raise Not_found let git_forge_url org = Printf.sprintf "https://%s.com/%s" M.prefix org diff --git a/web-ui/view/ref.ml b/web-ui/view/ref.ml index 70210a6c..eea410c0 100644 --- a/web-ui/view/ref.ml +++ b/web-ui/view/ref.ml @@ -8,7 +8,6 @@ module Make (M : Git_forge_intf.Forge) = struct let logo = match M.prefix with | "github" -> Common.github_logo - | "gitlab" -> Common.gitlab_logo | _ -> raise Not_found let row ~ref ~short_hash ~started_at ~ran_for ~status ~ref_uri ~message = @@ -122,7 +121,6 @@ module Make (M : Git_forge_intf.Forge) = struct | _, "refs" :: "heads" :: branch -> Branch (Astring.String.concat ~sep:"/" branch) | "github", [ "refs"; "pull"; id; "head" ] -> PR { title; id } - | "gitlab", [ "refs"; "merge-requests"; id; "head" ] -> PR { title; id } | _ -> Branch (Printf.sprintf "Bad ref format %S" gref) let list ~org ~repo ~default_ref ~refs = @@ -165,11 +163,6 @@ module Make (M : Git_forge_intf.Forge) = struct Client.Ref_map.filter (fun ref _ -> String.starts_with ~prefix:"refs/pull/" ref) refs - | "gitlab" -> - Client.Ref_map.filter - (fun ref _ -> - String.starts_with ~prefix:"refs/merge-requests/" ref) - refs | _ -> Client.Ref_map.empty (* FIXME: This should lead to an error. *) in let n_prs = Client.Ref_map.cardinal prs in diff --git a/web-ui/view/step.ml b/web-ui/view/step.ml index 4bad2613..e6307506 100644 --- a/web-ui/view/step.ml +++ b/web-ui/view/step.ml @@ -142,7 +142,6 @@ module Make (M : Git_forge_intf.Forge) = struct Common.form_cancel ~hash ~csrf_token ~show:can_cancel () :: Common.rebuild_button ~hash ~csrf_token ~show:show_rebuild () in - (* FIXME: Remove below when we are ready to show history for Gitlab as well. *) let title_card = Build.title_card ~status:build_status ~card_title:message ~hash_link:(link_forge_commit ~org ~repo ~hash:(Common.short_hash hash)) From 5543235bec2a45c22f16987d65a4098e68feaea5 Mon Sep 17 00:00:00 2001 From: Tim McGilchrist Date: Thu, 30 Apr 2026 09:48:04 +1000 Subject: [PATCH 3/5] Remove gitlab service and ocaml-ci-gitlab package. --- Dockerfile.gitlab | 45 ------- dune-project | 21 --- gitlab/dune | 72 ----------- gitlab/gitlab.ml | 17 --- gitlab/local.ml | 51 -------- gitlab/main.ml | 233 --------------------------------- gitlab/pipeline.ml | 299 ------------------------------------------- gitlab/pipeline.mli | 21 --- ocaml-ci-gitlab.opam | 43 ------- 9 files changed, 802 deletions(-) delete mode 100644 Dockerfile.gitlab delete mode 100644 gitlab/dune delete mode 100644 gitlab/gitlab.ml delete mode 100644 gitlab/local.ml delete mode 100644 gitlab/main.ml delete mode 100644 gitlab/pipeline.ml delete mode 100644 gitlab/pipeline.mli delete mode 100644 ocaml-ci-gitlab.opam diff --git a/Dockerfile.gitlab b/Dockerfile.gitlab deleted file mode 100644 index 2eca6715..00000000 --- a/Dockerfile.gitlab +++ /dev/null @@ -1,45 +0,0 @@ -# syntax=docker/dockerfile:1 -FROM ocaml/opam:debian-13-ocaml-4.14 AS build -RUN sudo ln -sf /usr/bin/opam-2.5 /usr/bin/opam && opam init --reinit -ni -RUN sudo rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' | sudo tee /etc/apt/apt.conf.d/keep-cache -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ - sudo apt update && sudo apt-get --no-install-recommends install -y \ - capnproto \ - graphviz \ - libev-dev \ - libgmp-dev \ - libsqlite3-dev \ - m4 \ - pkg-config -RUN cd ~/opam-repository && git fetch -q origin master && git reset --hard 94c943996066236b7203cad4027522be61e33f45 && opam update -COPY --chown=opam --link ocaml-ci.opam ocaml-ci-gitlab.opam ocaml-ci-service.opam ocaml-ci-api.opam /src/ -WORKDIR /src -ENV OPAMSOLVERTIMEOUT=900 -RUN --mount=type=cache,target=/home/opam/.opam/download-cache,sharing=locked,uid=1000,gid=1000 \ - opam install -y --deps-only . -ADD --chown=opam . . -RUN opam exec -- dune build ./_build/install/default/bin/ocaml-ci-gitlab - -FROM debian:13 -RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ - apt update && apt-get --no-install-recommends install -y \ - ca-certificates \ - curl \ - docker-cli \ - dumb-init \ - git \ - graphviz \ - libev4 \ - libsqlite3-dev \ - netbase \ - openssh-client -WORKDIR /var/lib/ocurrent -ENTRYPOINT ["dumb-init", "/usr/local/bin/ocaml-ci-gitlab"] -ENV OCAMLRUNPARAM=a=2 -# Enable experimental for docker manifest support -ENV DOCKER_CLI_EXPERIMENTAL=enabled -COPY --from=build --link /src/_build/install/default/bin/ocaml-ci-gitlab /usr/local/bin/ -COPY --from=build --link /src/migrations /migrations diff --git a/dune-project b/dune-project index 70a6638c..f05d5589 100644 --- a/dune-project +++ b/dune-project @@ -87,27 +87,6 @@ (prometheus-app (>= 1.2)) (timedesc (>= 0.9.0)))) -(package - (name ocaml-ci-gitlab) - (synopsis "Test OCaml projects on GitLab") - (description "Test OCaml projects on GitLab") - (depends - (ocaml (>= 4.14)) - dune - ocaml-ci - ocaml-ci-service - current - current_git - current_gitlab - current_docker - (alcotest (and (>= 1.7.0) :with-test)) - (cmdliner (>= 1.1.1)) - (fmt (>= 0.8.9)) - (gitlab-unix (>= 0.1.8)) - (logs (>= 0.7.0)) - (odoc :with-doc) - (prometheus-app (>= 1.2)))) - (package (name ocaml-ci-client) (synopsis "Command-line client for ocaml-ci") diff --git a/gitlab/dune b/gitlab/dune deleted file mode 100644 index b46de4e1..00000000 --- a/gitlab/dune +++ /dev/null @@ -1,72 +0,0 @@ -(library - (name ocaml_ci_gitlab) - (public_name ocaml-ci-gitlab) - (libraries - ocaml-ci - ocaml-ci-service - current - current_git - current_github - current_gitlab - current_docker - current_web - gitlab-unix - prometheus-app.unix - cmdliner) - (modules pipeline gitlab)) - -(executables - (names main local) - (libraries - logs.fmt - logs.cli - fmt.tty - fmt.cli - current - current_web - current_docker - current_git - current_github - current_gitlab - current_rpc - dockerfile-opam - ocluster-api - capnp-rpc-unix - mirage-crypto-rng.unix - ocaml-ci - ocaml-ci-api - ocaml_ci_service - ocaml_ci_gitlab - prometheus-app.unix) - (modules main local)) - -; This is a hack to work around https://github.com/ocaml/dune/issues/3499 -; We first build the binaries, then copy them to a new name, then install them. -; This allows us to add in the run-time dependency on solver-service in the -; copy step. - -(install - (section bin) - (package ocaml-ci-gitlab) - (files - (main-copy.exe as ocaml-ci-gitlab))) - -(install - (section bin) - (package ocaml-ci-gitlab) - (files - (local-copy.exe as ocaml-ci-gitlab-local))) - -(rule - (target main-copy.exe) - (deps - (package solver-service)) - (action - (copy main.exe main-copy.exe))) - -(rule - (target local-copy.exe) - (deps - (package solver-service)) - (action - (copy local.exe local-copy.exe))) diff --git a/gitlab/gitlab.ml b/gitlab/gitlab.ml deleted file mode 100644 index 504630e2..00000000 --- a/gitlab/gitlab.ml +++ /dev/null @@ -1,17 +0,0 @@ -(* Access control policy. *) -let has_role user = function - | `Viewer | `Monitor -> true - | `Builder | `Admin -> ( - match Option.map Current_web.User.id user with - | Some "gitlab:tmcgilchrist" -> true - | Some _ | None -> false) - -let webhook_route ~webhook_secret = - Routes.( - (s "webhooks" / s "gitlab" /? nil) - @--> Current_gitlab.webhook ~webhook_secret) - -let login_route gitlab_auth = - Routes.((s "login" /? nil) @--> Current_gitlab.Auth.login gitlab_auth) - -let authn auth = Option.map Current_gitlab.Auth.make_login_uri auth diff --git a/gitlab/local.ml b/gitlab/local.ml deleted file mode 100644 index 27a5fb9d..00000000 --- a/gitlab/local.ml +++ /dev/null @@ -1,51 +0,0 @@ -(* Utility program for testing the CI pipeline on a local repository. *) - -let setup_log default_level = - Unix.putenv "DOCKER_BUILDKIT" "1"; - Unix.putenv "PROGRESS_NO_TRUNC" "1"; - Prometheus_unix.Logging.init ?default_level () - -let main () config mode repo : ('a, [ `Msg of string ]) result = - let solver = Ocaml_ci.Backend_solver.v None in - let repo = Current_git.Local.v (Fpath.v repo) in - let engine = - Current.Engine.create ~config - (Ocaml_ci_gitlab.Pipeline.local_test ~query_uri:None ~solver repo) - in - let site = - Current_web.Site.(v ~has_role:allow_all) - ~name:"ocaml-ci-local" - (Current_web.routes engine) - in - Lwt_main.run - (Lwt.choose [ Current.Engine.thread engine; Current_web.run ~mode site ]) - -(* Command-line parsing *) - -open Cmdliner - -let setup_log = - let docs = Manpage.s_common_options in - Term.(const setup_log $ Logs_cli.level ~docs ()) - -let repo = - Arg.required - @@ Arg.pos 0 Arg.(some dir) None - @@ Arg.info ~doc:"The directory containing the .git subdirectory." ~docv:"DIR" - [] - -let cmd = - let doc = "Test ocaml-ci on a local Git clone" in - let info = - Cmd.info "ocaml-ci-local" ~doc ~envs:Ocaml_ci_service.Conf.cmdliner_envs - in - Cmd.v info - Term.( - term_result - (const main - $ setup_log - $ Current.Config.cmdliner - $ Current_web.cmdliner - $ repo)) - -let () = exit @@ Cmd.eval cmd diff --git a/gitlab/main.ml b/gitlab/main.ml deleted file mode 100644 index 0f6442f4..00000000 --- a/gitlab/main.ml +++ /dev/null @@ -1,233 +0,0 @@ -open Lwt.Infix -open Capnp_rpc_lwt - -module Metrics = struct - open Prometheus - open Ocaml_ci - - let namespace = "ocamlci" - let subsystem = "gitlab_pipeline" - - let master = - let help = "Number of master branches by state" in - Gauge.v_label ~label_name:"state" ~help ~namespace ~subsystem - "master_state_total" - - type stats = { ok : int; failed : int; active : int } - - let count_repo ~owner name (acc : stats) = - let repo = { Repo_id.owner; name } in - match - Index.Ref_map.find_opt "refs/heads/master" (Index.get_active_refs repo) - with - | None -> acc - | Some { Index.hash; _ } -> ( - match Index.Commit_cache.(find ~owner ~name ~hash |> get_status) with - | `Failed -> { acc with failed = acc.failed + 1 } - | `Passed -> { acc with ok = acc.ok + 1 } - | `Not_started | `Pending -> { acc with active = acc.active + 1 }) - - let count_owner owner (acc : stats) = - Index.Repo_set.fold (count_repo ~owner) (Index.get_active_repos ~owner) acc - - let update () = - let owners = Index.get_active_owners () in - let { ok; failed; active } = - Index.Owner_set.fold count_owner owners { ok = 0; failed = 0; active = 0 } - in - Gauge.set (master "ok") (float_of_int ok); - Gauge.set (master "failed") (float_of_int failed); - Gauge.set (master "active") (float_of_int active) -end - -(* TODO Isolate gitlab specific configuration from general service config. *) -module Conf = Ocaml_ci_service.Conf - -let setup_log default_level = - Prometheus_unix.Logging.init ?default_level (); - Mirage_crypto_rng_unix.initialize (module Mirage_crypto_rng.Fortuna); - Prometheus.CollectorRegistry.(register_pre_collect default) Metrics.update; - (match Conf.capnp_profile with - | `Production -> Logs.info (fun f -> f "Using production capnp configuration") - | `Dev -> Logs.info (fun f -> f "Using dev capnp configuration")); - match Conf.platforms_profile with - | `All -> Logs.info (fun f -> f "Testing all platforms") - | `Minimal -> Logs.info (fun f -> f "Testing minimal platforms") - -let or_die = function Ok x -> x | Error (`Msg m) -> failwith m - -let check_dir x = - Lwt.catch - (fun () -> - Lwt_unix.stat x >|= function - | Unix.{ st_kind = S_DIR; _ } -> `Present - | _ -> Fmt.failwith "Exists, but is not a directory: %S" x) - (function - | Unix.Unix_error (Unix.ENOENT, _, _) -> Lwt.return `Missing - | exn -> Lwt.fail exn) - -let ensure_dir path = - check_dir path >>= function - | `Present -> Lwt.return_unit - | `Missing -> Lwt_unix.mkdir path 0o777 - -let run_capnp capnp_public_address capnp_listen_address = - match (capnp_public_address, capnp_listen_address) with - | None, None -> Lwt.return (Capnp_rpc_unix.client_only_vat (), None) - | Some _, None -> - Lwt.fail_invalid_arg - "Public address for Cap'n Proto RPC can't be set without setting a \ - capnp-listen-address to listen on." - | Some _, Some _ | None, Some _ -> - let listen_address = - match capnp_listen_address with - | Some listen_address -> listen_address - | None -> - Capnp_rpc_unix.Network.Location.tcp ~host:"0.0.0.0" - ~port:Conf.Capnp.internal_port - in - let public_address = - match capnp_public_address with - | None -> listen_address - | Some public_address -> public_address - in - ensure_dir Conf.Capnp.cap_secrets >>= fun () -> - let config = - Capnp_rpc_unix.Vat_config.create ~public_address - ~secret_key:(`File Conf.Capnp.secret_key) listen_address - in - let rpc_engine, rpc_engine_resolver = Capability.promise () in - let service_id = Capnp_rpc_unix.Vat_config.derived_id config "ci" in - let restore = Capnp_rpc_net.Restorer.single service_id rpc_engine in - Capnp_rpc_unix.serve config ~restore >>= fun vat -> - Capnp_rpc_unix.Cap_file.save_service vat service_id - (Conf.Capnp.cap_secrets ^ "/ocaml-ci-gitlab-admin.cap") - |> or_die; - Logs.app (fun f -> - f "Wrote capability reference to %S" Conf.Capnp.cap_file); - Lwt.return (vat, Some rpc_engine_resolver) - -let main () config mode app capnp_public_address capnp_listen_address - gitlab_auth submission_uri solver_uri query_uri migrations : - ('a, [ `Msg of string ]) result = - let open Ocaml_ci_gitlab in - Lwt_main.run - (let solver = Ocaml_ci.Backend_solver.v solver_uri in - run_capnp capnp_public_address capnp_listen_address - >>= fun (vat, rpc_engine_resolver) -> - let ocluster = - Option.map (Capnp_rpc_unix.Vat.import_exn vat) submission_uri - in - let engine = - Current.Engine.create ~config - (Pipeline.v ?ocluster ~app ~solver ~query_uri ~migrations) - in - rpc_engine_resolver - |> Option.iter (fun r -> - Capability.resolve_ok r (Ocaml_ci_service.Api_impl.make_ci ~engine)); - let authn = Gitlab.authn gitlab_auth in - let webhook_secret = Current_gitlab.Api.webhook_secret app in - let has_role = - if gitlab_auth = None then Current_web.Site.allow_all - else Gitlab.has_role - in - let secure_cookies = gitlab_auth <> None in - let routes = - Gitlab.webhook_route ~webhook_secret - :: Gitlab.login_route gitlab_auth - :: Current_web.routes engine - in - let site = - Current_web.Site.v ?authn ~has_role ~secure_cookies - ~name:"ocaml-ci-gitlab" routes - in - Lwt.choose [ Current.Engine.thread engine; Current_web.run ~mode site ]) - -(* Command-line parsing *) - -open Cmdliner - -let setup_log = - let docs = Manpage.s_common_options in - Term.(const setup_log $ Logs_cli.level ~docs ()) - -let capnp_public_address = - Arg.value - @@ Arg.opt (Arg.some Capnp_rpc_unix.Network.Location.cmdliner_conv) None - @@ Arg.info - ~doc: - "Public address (SCHEME:HOST:PORT) for Cap'n Proto RPC (default: no \ - RPC).\n\ - \ If --capnp-listen-address isn't set it will run no RPC." - ~docv:"ADDR" [ "capnp-public-address" ] - -let capnp_listen_address = - let i = - Arg.info ~docv:"ADDR" - ~doc: - "Address to listen on, e.g. $(b,unix:/run/my.socket) (default: no RPC)." - [ "capnp-listen-address" ] - in - Arg.( - value - @@ opt (Arg.some Capnp_rpc_unix.Network.Location.cmdliner_conv) None - @@ i) - -let submission_service = - Arg.value - @@ Arg.opt Arg.(some Capnp_rpc_unix.sturdy_uri) None - @@ Arg.info ~doc:"The submission.cap file for the build scheduler service" - ~docv:"FILE" [ "submission-service" ] - -let migrations = - Arg.( - value - @@ opt (some dir) None - @@ info ~docv:"MIGRATIONS_PATH" - ~doc: - "Specify the path to the migration directory. If no path is given \ - the migration step is ignored." - [ "migration-path" ]) - -let submission_solver_service = - Arg.value - @@ Arg.opt Arg.(some Capnp_rpc_unix.sturdy_uri) None - @@ Arg.info - ~doc: - "The submission-solve.cap file for a scheduler service which handles \ - a solver-worker. The cap file could be the same as \ - $(b,--submission-service)." - ~docv:"FILE" - [ "submission-solver-service" ] - -let submission_query_service = - Arg.value - @@ Arg.opt Arg.(some Capnp_rpc_unix.sturdy_uri) None - @@ Arg.info - ~doc: - "The query-solve.cap file which handles building opam variables for \ - various platforms. The cap file could be the same as \ - $(b,--submission-service) or omitted to use the local system." - ~docv:"FILE" - [ "submission-query-service" ] - -let cmd = - let doc = "Build OCaml projects on GitLab" in - let info = Cmd.info "ocaml-ci-gitlab" ~doc in - Cmd.v info - Term.( - term_result - (const main - $ setup_log - $ Current.Config.cmdliner - $ Current_web.cmdliner - $ Current_gitlab.Api.cmdliner - $ capnp_public_address - $ capnp_listen_address - $ Current_gitlab.Auth.cmdliner - $ submission_service - $ submission_solver_service - $ submission_query_service - $ migrations)) - -let () = exit @@ Cmd.eval cmd diff --git a/gitlab/pipeline.ml b/gitlab/pipeline.ml deleted file mode 100644 index 8adfea6c..00000000 --- a/gitlab/pipeline.ml +++ /dev/null @@ -1,299 +0,0 @@ -open Current.Syntax -open Ocaml_ci -open Pipeline -module Conf = Ocaml_ci_service.Conf -module Git = Current_git -module Gitlab = Current_gitlab -module Docker = Current_docker.Default - -module Metrics = struct - open Prometheus - - let namespace = "ocurrent" - let subsystem = "gitlab" - - let repositories_total = - let help = "Total number of active repositories" in - Gauge.v_label ~label_name:"account" ~help ~namespace ~subsystem - "repositories_total" -end - -let program_name = "ocaml-ci" - -(* Link for GitLab statuses. *) -let url ~owner ~name ~hash = - Uri.of_string - (Printf.sprintf "%s/gitlab/%s/%s/commit/%s" Conf.website_scheme_and_domain - owner name hash) - -(* Watch the opam-repository for changes. *) -let opam_repository_commit = - let module Github = Current_github in - let repo = { Github.Repo_id.owner = "ocaml"; name = "opam-repository" } in - Github.Api.Anonymous.head_of repo @@ `Ref "refs/heads/master" - -(* TODO Sometime later, grab these from Index/DB table. *) -let gitlab_repos : Gitlab.Repo_id.t list = - [ - { - Gitlab.Repo_id.owner = "tmcgilchrist"; - Gitlab.Repo_id.name = "ocaml-gitlab"; - project_id = 29798678; - }; - { - Gitlab.Repo_id.owner = "talex5"; - Gitlab.Repo_id.name = "gemini-eio"; - project_id = 28169706; - }; - { - Gitlab.Repo_id.owner = "nomadic-labs"; - Gitlab.Repo_id.name = "resto"; - project_id = 16269987; - }; - { - Gitlab.Repo_id.owner = "nomadic-labs"; - Gitlab.Repo_id.name = "data-encoding"; - project_id = 14134943; - }; - { - Gitlab.Repo_id.owner = "nomadic-labs"; - Gitlab.Repo_id.name = "ringo"; - project_id = 15200071; - }; - { - Gitlab.Repo_id.owner = "nomadic-labs"; - Gitlab.Repo_id.name = "ocaml-secp256k1-internal"; - project_id = 17524462; - }; - { - Gitlab.Repo_id.owner = "nomadic-labs"; - Gitlab.Repo_id.name = "lwt-exit"; - project_id = 22943026; - }; - { - Gitlab.Repo_id.owner = "nomadic-labs"; - Gitlab.Repo_id.name = "lwt-watcher"; - project_id = 14672501; - }; - { - Gitlab.Repo_id.owner = "nomadic-labs"; - Gitlab.Repo_id.name = "lwt-canceler"; - project_id = 14702762; - }; - { - Gitlab.Repo_id.owner = "nomadic-labs"; - Gitlab.Repo_id.name = "ctypes_stubs_js"; - project_id = 31956063; - }; - { - Gitlab.Repo_id.owner = "nomadic-labs"; - Gitlab.Repo_id.name = "seqes"; - project_id = 41118330; - }; - { - Gitlab.Repo_id.owner = "nomadic-labs"; - Gitlab.Repo_id.name = "tezt"; - project_id = 41852995; - }; - { - Gitlab.Repo_id.owner = "raphael-proust"; - Gitlab.Repo_id.name = "queenshead"; - project_id = 50742201; - }; - { - Gitlab.Repo_id.owner = "raphael-proust"; - Gitlab.Repo_id.name = "entombed"; - project_id = 30307357; - }; - ] - -(* Fake Installation module, we don't have this in GitLab. *) -module Installation = struct - type t = { name : string } - - let compare = compare - let pp f t = Fmt.string f t.name -end - -let gitlab_installations = - gitlab_repos - |> List.map (fun x -> { Installation.name = x.Gitlab.Repo_id.owner }) - |> List.sort_uniq Installation.compare - -let set_active_installations (accounts : Installation.t list Current.t) = - let+ accounts in - accounts - |> List.fold_left - (fun acc i -> Index.Owner_set.add i.Installation.name acc) - Index.Owner_set.empty - |> Index.set_active_owners; - accounts - -let set_active_repos ~installation repos = - let+ repos and+ installation in - repos - |> List.fold_left - (fun acc r -> Index.Repo_set.add r.Gitlab.Repo_id.name acc) - Index.Repo_set.empty - |> Index.set_active_repos ~owner:installation.Installation.name; - Prometheus.Gauge.set - (Metrics.repositories_total installation.Installation.name) - (float_of_int (List.length repos)); - repos - -let gref_from_commit x = Git.Commit_id.gref @@ Gitlab.Api.Commit.id x - -let ref_name c = - match (Gitlab.Api.Commit.branch_name c, Gitlab.Api.Commit.mr_name c) with - | Some name, None | None, Some name -> name - | _ -> failwith "Commit is neither a branch nor a MR" - -let set_active_refs ~(repo : Gitlab.Repo_id.t Current.t) ~default commits = - let+ repo and+ commits and+ default in - let repo = { Repo_id.owner = repo.owner; name = repo.name } in - let refs = - commits - |> List.fold_left - (fun acc commit -> - let commit_id = Gitlab.Api.Commit.id commit in - let gref = Git.Commit_id.gref commit_id in - let hash = Git.Commit_id.hash commit_id in - let name = ref_name commit in - let message = Gitlab.Api.Commit.message commit in - Index.Ref_map.add gref { Index.hash; message; name } acc) - Index.Ref_map.empty - in - let default_gref = gref_from_commit default in - Index.set_active_refs ~repo refs default_gref; - commits - -let repositories (installation : Installation.t Current.t) = - let+ installation in - List.filter - (fun repo -> repo.Gitlab.Repo_id.owner == installation.name) - gitlab_repos - -let gitlab_status_of_state head result = - let+ head and+ result in - let { Gitlab.Repo_id.owner; name; project_id = _ } = - Gitlab.Api.Commit.repo_id head - in - let hash = Gitlab.Api.Commit.hash head in - let url = url ~owner ~name ~hash in - match result with - | Ok () -> - Gitlab.Api.Status.v ~url `Success ~description:"Passed" ~name:program_name - | Error (`Active _) -> Gitlab.Api.Status.v ~url `Pending ~name:program_name - | Error (`Msg m) when Astring.String.is_prefix ~affix:"[SKIP]" m -> - Gitlab.Api.Status.v ~url `Success ~description:m ~name:program_name - | Error (`Msg m) -> - Gitlab.Api.Status.v ~url `Failure ~description:m ~name:program_name - -let local_test ~query_uri ~solver repo () = - let platforms = - Conf.fetch_platforms ~query_uri ~include_macos:false ~include_freebsd:false - ~include_windows:false ~include_openbsd:false () - in - let src = Git.Local.head_commit repo in - let src_content = Repo_content.extract src in - let repo = Current.return { Repo_id.owner = "local"; name = "test" } - and analysis = - Analyse.examine ~solver ~platforms ~opam_repository_commit src src_content - in - Current.component "summarise" - |> let> results = build_with_docker ~repo ~analysis ~platforms src in - let result = summarise results in - Current_incr.const (result, None) - -let v ?ocluster ~app ~query_uri ~solver ~migrations () = - let platforms = - Conf.fetch_platforms ~query_uri ~include_macos:true ~include_freebsd:true - ~include_windows:true ~include_openbsd:true () - in - let ocluster = - Option.map (Cluster_build.config ~timeout:(Duration.of_hour 1)) ocluster - in - let migrations = - match migrations with - | Some path -> Index.migrate path - | None -> Current.return () - in - Current.with_context migrations @@ fun () -> - Current.with_context opam_repository_commit @@ fun () -> - Current.with_context platforms @@ fun () -> - Current.return gitlab_installations - |> set_active_installations - |> Current.list_iter ~collapse_key:"org" (module Installation) - @@ fun installation -> - repositories installation - |> set_active_repos ~installation - |> Current.list_iter ~collapse_key:"repo" (module Gitlab.Repo_id) - @@ fun repo -> - let* repo_id = repo in - let default = Gitlab.Api.head_commit app repo_id in - let refs = - let refs = - Gitlab.Api.ci_refs app ~staleness:Conf.max_staleness repo_id - in - set_active_refs ~repo ~default refs - in - refs - |> Current.list_iter (module Gitlab.Api.Commit) @@ fun head -> - let src = Git.fetch (Current.map Gitlab.Api.Commit.id head) in - let src_content = Repo_content.extract src in - let analysis = - Analyse.examine ~solver ~platforms ~opam_repository_commit src - src_content - in - let* on_cancel = - match ocluster with - | None -> Current.return None - | Some _ -> - let+ commit = head in - let gref = Git.Commit_id.gref @@ Gitlab.Api.Commit.id commit in - let repo = Gitlab.Api.Commit.repo_id commit in - let repo = { Repo_id.owner = repo.owner; name = repo.name } in - let hash = Gitlab.Api.Commit.hash commit in - Some - (fun _ -> Index.record_summary_on_cancel ~repo ~gref ~hash) - in - let builds = - let repo = - Current.map - (fun repo -> - { - Repo_id.owner = repo.Gitlab.Repo_id.owner; - name = repo.name; - }) - repo - in - build_with_docker ?ocluster ?on_cancel ~repo ~analysis ~platforms - src - in - let summary = Current.map summarise builds in - let status = - let+ summary in - match summary with - | Ok () -> `Passed - | Error (`Active `Running) -> `Pending - | Error (`Msg _) -> `Failed - in - let index = - let+ commit = head and+ builds and+ status in - let gref = Git.Commit_id.gref @@ Gitlab.Api.Commit.id commit in - let repo = - Gitlab.Api.Commit.repo_id commit |> fun repo -> - { Repo_id.owner = repo.owner; name = repo.name } - in - let hash = Gitlab.Api.Commit.hash commit in - let jobs = - List.map - (fun (s, (_, job_id)) -> (s.Build_info.label, job_id)) - builds - in - Index.record ~repo ~hash ~status ~gref jobs - and set_gitlab_status = - gitlab_status_of_state head summary - |> Gitlab.Api.Commit.set_status head "ocaml-ci" - in - Current.all [ index; set_gitlab_status ] diff --git a/gitlab/pipeline.mli b/gitlab/pipeline.mli deleted file mode 100644 index c028b883..00000000 --- a/gitlab/pipeline.mli +++ /dev/null @@ -1,21 +0,0 @@ -(** Pipeline for testing GitLab hosted repositories. *) - -val local_test : - query_uri:Uri.t option -> - solver:Ocaml_ci.Backend_solver.t -> - Current_git.Local.t -> - unit -> - unit Current.t -(** [local_test ~solver repo] is a pipeline that tests local repository [repo] - as the GitLab CI pipeline would. *) - -val v : - ?ocluster:Cluster_api.Raw.Client.Submission.t Capnp_rpc_lwt.Sturdy_ref.t -> - app:Current_gitlab.Api.t -> - query_uri:Uri.t option -> - solver:Ocaml_ci.Backend_solver.t -> - migrations:string option -> - unit -> - unit Current.t -(** The main ocaml-ci pipeline for testing GitLab hosted repositories. If - [migration] is [Some path], it automatically executes the migrations.*) diff --git a/ocaml-ci-gitlab.opam b/ocaml-ci-gitlab.opam deleted file mode 100644 index 0d39ccb6..00000000 --- a/ocaml-ci-gitlab.opam +++ /dev/null @@ -1,43 +0,0 @@ -# This file is generated by dune, edit dune-project instead -opam-version: "2.0" -synopsis: "Test OCaml projects on GitLab" -description: "Test OCaml projects on GitLab" -maintainer: ["Mark.Elvers "] -authors: ["talex5@gmail.com"] -license: "MIT" -homepage: "https://github.com/ocurrent/ocaml-ci" -doc: "https://ocaml.ci.dev/documentation" -bug-reports: "https://github.com/ocurrent/ocaml-ci/issues" -depends: [ - "ocaml" {>= "4.14"} - "dune" {>= "3.20"} - "ocaml-ci" - "ocaml-ci-service" - "current" - "current_git" - "current_gitlab" - "current_docker" - "alcotest" {>= "1.7.0" & with-test} - "cmdliner" {>= "1.1.1"} - "fmt" {>= "0.8.9"} - "gitlab-unix" {>= "0.1.8"} - "logs" {>= "0.7.0"} - "odoc" {with-doc} - "prometheus-app" {>= "1.2"} -] -build: [ - ["dune" "subst"] {dev} - [ - "dune" - "build" - "-p" - name - "-j" - jobs - "@install" - "@runtest" {with-test} - "@doc" {with-doc} - ] -] -dev-repo: "git+https://github.com/ocurrent/ocaml-ci.git" -x-maintenance-intent: ["(latest)"] From b6ea2489540d0259bf3ba78e785b838f2a848ec2 Mon Sep 17 00:00:00 2001 From: Tim McGilchrist Date: Thu, 30 Apr 2026 10:03:19 +1000 Subject: [PATCH 4/5] Remove GitLab references in web-ui documentation Clean up other references in README.md and developer documentation. --- README.md | 3 +-- doc/gitlab-dev.md | 18 ------------------ web-ui/README.md | 2 +- web-ui/view/documentation.ml | 2 +- web-ui/view/documentation/build_page.ml | 4 ++-- web-ui/view/documentation/index_page.ml | 15 ++------------- web-ui/view/documentation/user_guide.ml | 4 ++-- 7 files changed, 9 insertions(+), 39 deletions(-) delete mode 100644 doc/gitlab-dev.md diff --git a/README.md b/README.md index 97ea021f..b065b33f 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,6 @@ If you want to build the whole system, the easiest way is using Docker: ```sh docker build -f Dockerfile -t ocaml-ci-service . -docker build -f Dockerfile.gitlab -t ocaml-ci-gitlab . docker build -f Dockerfile.web -t ocaml-ci-web . ``` @@ -136,7 +135,7 @@ $ ocaml-ci mirage/irmin pull/867 alpine-3.10-ocaml-4.08 cancel ## Deployment -`ocaml-ci` is deployed as three docker images built from `Dockerfile`, `Dockerfile.gitlab` and `Dockerfile.web`, with +`ocaml-ci` is deployed as two docker images built from `Dockerfile` and `Dockerfile.web`, with the live service following `live-engine` for the backend and `live-www` for the frontend. An ocurrent-deployer [pipeline](deploy.ci.dev) watches these branches, performing a docker build and deploy whenever it sees a new commit. The live branches should typically contain commits from `master` plus potentially diff --git a/doc/gitlab-dev.md b/doc/gitlab-dev.md deleted file mode 100644 index d364be2d..00000000 --- a/doc/gitlab-dev.md +++ /dev/null @@ -1,18 +0,0 @@ -## Running the GitLab pipeline locally - -You will need the following: - -1. The GitLab API token with permissions to the repositories to build -2. GitLab secret associated with webhooks -3. A capability file for submitting jobs to a cluster, in this case the main ocaml-ci cluster as documented in https://github.com/ocurrent/ocluster#admin - -``` shell -dune exec -- ocaml-ci-gitlab \ - --gitlab-token-file \ - --gitlab-webhook-secret-file \ - --submission-service \ - --capnp-listen-address tcp:127.0.0.1:9800 - --migration-path "$PWD/migrations" -``` - -This will generate a capability file. See the logs for `Wrote capability reference to "./capnp-secrets/ocaml-ci-gitlab-admin.cap"` diff --git a/web-ui/README.md b/web-ui/README.md index 9127b9c3..f7f7bb46 100644 --- a/web-ui/README.md +++ b/web-ui/README.md @@ -13,7 +13,7 @@ These are some principles for `web-ui` that we will hold to: There should be no template or markup related code here. - Routes are in `web-ui/router.ml` -- these call controllers. - Concerns related to serialising and deserialising to JSON are in `representation` - - A `Git_forge` functorises controllers and views allowing for implementation of GitHub and GitLab git-forges. + - A `Git_forge` functorises controllers and views allowing for implementation of git-forges (currently GitHub). The dependencies in web-ui are described below: diff --git a/web-ui/view/documentation.ml b/web-ui/view/documentation.ml index 9c33fd7d..fe8bda5c 100644 --- a/web-ui/view/documentation.ml +++ b/web-ui/view/documentation.ml @@ -278,7 +278,7 @@ let user_guide website_scheme_and_domain = txt " : An organisation that owns projects that they want to \ build. This typically corresponds to an account on GitHub \ - or GitLab for example."; + for example."; ]; p [ diff --git a/web-ui/view/documentation/build_page.ml b/web-ui/view/documentation/build_page.ml index f70b64a8..3e0fe6a8 100644 --- a/web-ui/view/documentation/build_page.ml +++ b/web-ui/view/documentation/build_page.ml @@ -12,8 +12,8 @@ let show = repository's health following the additional changes introduced by \ the commit. It shows the overall status as well as an overview of \ the steps that constitute the build. When examining a commit on \ - GitHub or GitLab, clicking on the commit's build status will bring \ - you to this page."; + GitHub, clicking on the commit's build status will bring you to \ + this page."; ]; img ~a:[ a_class [ "border border-solid" ] ] diff --git a/web-ui/view/documentation/index_page.ml b/web-ui/view/documentation/index_page.ml index c5d227ce..f6226431 100644 --- a/web-ui/view/documentation/index_page.ml +++ b/web-ui/view/documentation/index_page.ml @@ -17,22 +17,11 @@ let show = [ txt "The index page of OCaml-CI lists all organisations that OCaml-CI \ - knows about, across both GitHub and GitLab. The logo of the Git \ - forge is used to indicate the source of the organisation. Note that \ - if no repositories are registered with OCaml-CI, the organisation \ - will not be listed."; + knows about. Note that if no repositories are registered with \ + OCaml-CI, the organisation will not be listed."; ]; img ~a:[ a_class [ "border border-solid" ] ] ~src:"/images/index-page-overview.png" ~alt:"index-page-overview" (); - p - [ - txt - "A filter is provided to select between GitHub or GitLab. Search is \ - also available."; - ]; - img - ~a:[ a_class [ "border border-solid" ] ] - ~src:"/images/index-page-search.png" ~alt:"index-page-search" (); p [ txt "To see its repositories, click on the organisation." ]; ] diff --git a/web-ui/view/documentation/user_guide.ml b/web-ui/view/documentation/user_guide.ml index 75acc3de..cab48831 100644 --- a/web-ui/view/documentation/user_guide.ml +++ b/web-ui/view/documentation/user_guide.ml @@ -130,8 +130,8 @@ let concepts_and_terms = [ strong [ txt "Git forge" ]; txt - " : A service that hosts Git repositories. Currently GitHub and \ - GitLab are supported."; + " : A service that hosts Git repositories. Currently GitHub is \ + supported."; ]; p [ From f1b83c26ee30751fcb83e576c78e437ccc80c3c0 Mon Sep 17 00:00:00 2001 From: Mark Elvers Date: Thu, 28 May 2026 10:56:40 +0000 Subject: [PATCH 5/5] Remove leftover GitLab references Clean up two .mli doc-comments and two unused static images that were missed by the prior GitLab removal commits. --- lib/build.mli | 3 +-- lib/cluster_build.mli | 3 +-- web-ui/static/images/gitlab-logo-500.png | Bin 5511 -> 0 bytes web-ui/static/images/index-page-search.png | Bin 97326 -> 0 bytes web-ui/view/organisation.ml | 4 +--- web-ui/view/ref.ml | 4 +--- 6 files changed, 4 insertions(+), 10 deletions(-) delete mode 100644 web-ui/static/images/gitlab-logo-500.png delete mode 100644 web-ui/static/images/index-page-search.png diff --git a/lib/build.mli b/lib/build.mli index d7a49ea1..efc66108 100644 --- a/lib/build.mli +++ b/lib/build.mli @@ -8,8 +8,7 @@ val v : ([> `Built | `Checked ] Current_term.Output.t * Current.job_id option) Current.t (** Build and test all the opam packages in a given build context on the given - platform. [~repo] is the ID of the repository-under-test on a Git Forge - (e.g. GitHub or GitLab). *) + platform. [~repo] is the ID of the repository-under-test on GitHub. *) val make_build_spec : base:Current_docker.Raw.Image.t -> diff --git a/lib/cluster_build.mli b/lib/cluster_build.mli index 51e501af..fdc64592 100644 --- a/lib/cluster_build.mli +++ b/lib/cluster_build.mli @@ -17,8 +17,7 @@ val v : ([> `Built | `Checked ] Current_term.Output.t * Current.job_id option) Current.t (** Build and test all the opam packages in a given build context on the given - platform. [~repo] is the ID of the repository-under-test on a Git Forge - (e.g. GitHub or GitLab). + platform. [~repo] is the ID of the repository-under-test on GitHub. @param on_cancel The callback function to call if the job is cancelled. @param repo The ID of the repository-under-test on GitHub. *) diff --git a/web-ui/static/images/gitlab-logo-500.png b/web-ui/static/images/gitlab-logo-500.png deleted file mode 100644 index b4c82efed9875acdfe2ac761d6797ba3bf76d1c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5511 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 diff --git a/web-ui/view/organisation.ml b/web-ui/view/organisation.ml index 6caeb5fa..65f296e8 100644 --- a/web-ui/view/organisation.ml +++ b/web-ui/view/organisation.ml @@ -25,9 +25,7 @@ module Make (M : Forge_prefix) = struct ()) let logo = - match M.prefix with - | "github" -> Common.github_logo - | _ -> raise Not_found + match M.prefix with "github" -> Common.github_logo | _ -> raise Not_found let git_forge_url org = Printf.sprintf "https://%s.com/%s" M.prefix org diff --git a/web-ui/view/ref.ml b/web-ui/view/ref.ml index eea410c0..b9d509e1 100644 --- a/web-ui/view/ref.ml +++ b/web-ui/view/ref.ml @@ -6,9 +6,7 @@ module Make (M : Git_forge_intf.Forge) = struct type t = Branch of string | PR of { title : string; id : string } let logo = - match M.prefix with - | "github" -> Common.github_logo - | _ -> raise Not_found + match M.prefix with "github" -> Common.github_logo | _ -> raise Not_found let row ~ref ~short_hash ~started_at ~ran_for ~status ~ref_uri ~message = let ref_title =