func configureRoutes()

in workhorse/internal/upstream/routes.go [232:575]


func configureRoutes(u *upstream) {
	api := u.APIClient
	static := &staticpages.Static{DocumentRoot: u.DocumentRoot, Exclude: staticExclude}
	dependencyProxyInjector := dependencyproxy.NewInjector()
	proxy := buildProxy(u.Backend, u.Version, u.RoundTripper, u.Config, dependencyProxyInjector)
	cableProxy := proxypkg.NewProxy(u.CableBackend, u.Version, u.CableRoundTripper)

	assetsNotFoundHandler := NotFoundUnless(u.DevelopmentMode, proxy)
	if u.AltDocumentRoot != "" {
		altStatic := &staticpages.Static{DocumentRoot: u.AltDocumentRoot, Exclude: staticExclude}
		assetsNotFoundHandler = altStatic.ServeExisting(
			u.URLPrefix,
			staticpages.CacheExpireMax,
			NotFoundUnless(u.DevelopmentMode, proxy),
		)
	}

	signingTripper := secret.NewRoundTripper(u.RoundTripper, u.Version)
	signingProxy := buildProxy(u.Backend, u.Version, signingTripper, u.Config, dependencyProxyInjector)

	preparer := upload.NewObjectStoragePreparer(u.Config)
	requestBodyUploader := upload.RequestBody(api, signingProxy, preparer)
	mimeMultipartUploader := upload.Multipart(api, signingProxy, preparer, &u.Config)

	tempfileMultipartProxy := upload.FixedPreAuthMultipart(api, proxy, preparer, &u.Config)
	ciAPIProxyQueue := queueing.QueueRequests("ci_api_job_requests", tempfileMultipartProxy, u.APILimit, u.APIQueueLimit, u.APIQueueTimeout, prometheus.DefaultRegisterer)
	ciAPILongPolling := builds.RegisterHandler(ciAPIProxyQueue, u.watchKeyHandler, u.APICILongPollingDuration)

	dependencyProxyInjector.SetUploadHandler(requestBodyUploader)

	// Serve static files or forward the requests
	defaultUpstream := static.ServeExisting(
		u.URLPrefix,
		staticpages.CacheDisabled,
		static.DeployPage(static.ErrorPagesUnless(u.DevelopmentMode, staticpages.ErrorFormatHTML, tempfileMultipartProxy)),
	)
	probeUpstream := static.ErrorPagesUnless(u.DevelopmentMode, staticpages.ErrorFormatJSON, proxy)
	healthUpstream := static.ErrorPagesUnless(u.DevelopmentMode, staticpages.ErrorFormatText, proxy)

	gob := gobpkg.NewProxy(api, u.Version, u.ProxyHeadersTimeout, u.Config)

	u.Routes = []routeEntry{
		// Git Clone
		u.route("GET",
			newRoute(gitProjectPattern+`info/refs\z`, "git_info_refs", gitalyBackend),
			git.GetInfoRefsHandler(api)),
		u.route("POST",
			newRoute(gitProjectPattern+`git-upload-pack\z`, "git_upload_pack", gitalyBackend),
			contentEncodingHandler(git.UploadPack(api)), withMatcher(isContentType("application/x-git-upload-pack-request"))),
		u.route("POST",
			newRoute(gitProjectPattern+`git-receive-pack\z`, "git_receive_pack", gitalyBackend),
			contentEncodingHandler(git.ReceivePack(api)), withMatcher(isContentType("application/x-git-receive-pack-request"))),
		u.route("PUT",
			newRoute(gitProjectPattern+`gitlab-lfs/objects/([0-9a-f]{64})/([0-9]+)\z`, "git_lfs_objects", railsBackend),
			requestBodyUploader, withMatcher(isContentType("application/octet-stream"))),
		u.route("POST",
			newRoute(gitProjectPattern+`ssh-upload-pack\z`, "ssh_upload_pack", gitalyBackend),
			git.SSHUploadPack(api)),
		u.route("POST",
			newRoute(gitProjectPattern+`ssh-receive-pack\z`, "ssh_receive_pack", gitalyBackend),
			git.SSHReceivePack(api)),

		// CI Artifacts
		u.route("POST",
			newRoute(apiPattern+`v4/jobs/[0-9]+/artifacts\z`, "api_jobs_request", railsBackend),
			contentEncodingHandler(upload.Artifacts(api, signingProxy, preparer, &u.Config))),

		// ActionCable websocket
		u.wsRoute(newRoute(`^/-/cable\z`, "action_cable", railsBackend),
			cableProxy),

		// Terminal websocket
		u.wsRoute(
			newRoute(projectPattern+`-/environments/[0-9]+/terminal.ws\z`, "project_environments_terminal_ws", railsBackend),
			channel.Handler(api)),
		u.wsRoute(newRoute(projectPattern+`-/jobs/[0-9]+/terminal.ws\z`, "project_jobs_terminal_ws", railsBackend),
			channel.Handler(api)),

		// Proxy Job Services
		u.wsRoute(
			newRoute(projectPattern+`-/jobs/[0-9]+/proxy.ws\z`, "project_jobs_proxy_ws", railsBackend),
			channel.Handler(api)),

		// Long poll and limit capacity given to jobs/request and builds/register.json
		u.route("",
			newRoute(apiPattern+`v4/jobs/request\z`, "api_jobs_request", railsBackend), ciAPILongPolling),

		// Not all API endpoints support encoded project IDs
		// (e.g. `group%2Fproject`), but for the sake of consistency we
		// use the apiProjectPattern regex throughout. API endpoints
		// that do not support this will return 400 regardless of
		// whether they are accelerated by Workhorse or not.  See
		// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56731.

		// Maven Artifact Repository
		u.route("PUT",
			newRoute(apiProjectPattern+`/packages/maven/`, "projects_api_packages_maven", railsBackend), requestBodyUploader),

		// Conan Artifact Repository
		u.route("PUT",
			newRoute(apiPattern+`v4/packages/conan/`, "api_packages_conan", railsBackend), requestBodyUploader),
		u.route("PUT",
			newRoute(apiProjectPattern+`/packages/conan/`, "project_api_packages_conan", railsBackend), requestBodyUploader),

		// Generic Packages Repository
		u.route("PUT",
			newRoute(apiProjectPattern+`/packages/generic/`, "api_projects_packages_generic", railsBackend), requestBodyUploader),

		// Ml Model Packages Repository
		u.route("PUT",
			newRoute(apiProjectPattern+`/packages/ml_models/`, "api_projects_packages_ml_models", railsBackend), requestBodyUploader),

		// NuGet Artifact Repository
		u.route("PUT",
			newRoute(apiProjectPattern+`/packages/nuget/`, "api_projects_packages_nuget", railsBackend), mimeMultipartUploader),

		// NuGet v2 Artifact Repository
		u.route("PUT",
			newRoute(apiProjectPattern+`/packages/nuget/v2`, "api_projects_packages_nuget_v2", railsBackend), mimeMultipartUploader),

		// PyPI Artifact Repository
		u.route("POST",
			newRoute(apiProjectPattern+`/packages/pypi`, "api_projects_packages_pypi", railsBackend), mimeMultipartUploader),

		// Debian Artifact Repository
		u.route("PUT",
			newRoute(apiProjectPattern+`/packages/debian/`, "api_projects_packages_debian", railsBackend), requestBodyUploader),

		// RPM Artifact Repository
		u.route("POST",
			newRoute(apiProjectPattern+`/packages/rpm/`, "api_projects_packages_rpm", railsBackend), requestBodyUploader),

		// Gem Artifact Repository
		u.route("POST",
			newRoute(apiProjectPattern+`/packages/rubygems/`, "api_projects_packages_rubygems", railsBackend), requestBodyUploader),

		// Terraform Module Package Repository
		u.route("PUT",
			newRoute(apiProjectPattern+`/packages/terraform/modules/`, "api_projects_packages_terraform", railsBackend), requestBodyUploader),

		// Helm Artifact Repository
		u.route("POST",
			newRoute(apiProjectPattern+`/packages/helm/api/[^/]+/charts\z`, "api_projects_packages_helm", railsBackend), mimeMultipartUploader),

		// We are porting API to disk acceleration
		// we need to declare each routes until we have fixed all the routes on the rails codebase.
		// Overall status can be seen at https://gitlab.com/groups/gitlab-org/-/epics/1802#current-status
		u.route("POST",
			newRoute(apiProjectPattern+`/wikis/attachments\z`, "api_projects_wikis_attachments", railsBackend), tempfileMultipartProxy),
		u.route("POST",
			newRoute(apiGroupPattern+`/wikis/attachments\z`, "api_groups_wikis_attachments", railsBackend), tempfileMultipartProxy),
		u.route("POST",
			newRoute(apiPattern+`graphql\z`, "api_graphql", railsBackend), tempfileMultipartProxy),
		u.route("POST",
			newRoute(apiTopicPattern, "api_topics", railsBackend), tempfileMultipartProxy),
		u.route("PUT",
			newRoute(apiTopicPattern, "api_topics", railsBackend), tempfileMultipartProxy),
		u.route("POST",
			newRoute(apiPattern+`v4/groups/import`, "api_groups_import", railsBackend), mimeMultipartUploader),
		u.route("POST",
			newRoute(apiPattern+`v4/projects/import`, "api_projects_import", railsBackend), mimeMultipartUploader),
		u.route("POST",
			newRoute(apiPattern+`v4/projects/import-relation`, "api_projects_import_relation", railsBackend), mimeMultipartUploader),
		u.route("POST",
			newRoute(apiGroupPattern+`/placeholder_reassignments`, "api_group_placeholder_assignment", railsBackend), mimeMultipartUploader),
		u.route("POST",
			newRoute(groupPattern+`-/group_members/bulk_reassignment_file`, "group_placeholder_assignment", railsBackend), mimeMultipartUploader),
		// Project Import via UI upload acceleration
		u.route("POST",
			newRoute(importPattern+`gitlab_project`, "import_gitlab_project", railsBackend), mimeMultipartUploader),
		// Group Import via UI upload acceleration
		u.route("POST",
			newRoute(importPattern+`gitlab_group`, "import_gitlab_group", railsBackend), mimeMultipartUploader),

		// Issuable Metric image upload
		u.route("POST",
			newRoute(apiProjectPattern+`/issues/[0-9]+/metric_images\z`, "api_projects_issues_metric_images", railsBackend), mimeMultipartUploader),

		// Alert Metric image upload
		u.route("POST",
			newRoute(apiProjectPattern+`/alert_management_alerts/[0-9]+/metric_images\z`, "api_projects_alert_management_alerts_metric_images", railsBackend), mimeMultipartUploader),

		// Requirements Import via UI upload acceleration
		u.route("POST",
			newRoute(projectPattern+`requirements_management/requirements/import_csv`, "project_requirements_import_csv", railsBackend), mimeMultipartUploader),

		// Work items Import via UI upload acceleration
		u.route("POST",
			newRoute(projectPattern+`work_items/import_csv`, "project_work_items_import_csv", railsBackend), mimeMultipartUploader),

		// Uploads via API
		u.route("POST",
			newRoute(apiProjectPattern+`/uploads\z`, "api_projects_uploads", railsBackend), mimeMultipartUploader),

		// Project Avatar
		u.route("POST",
			newRoute(apiPattern+`v4/projects\z`, "api_projects", railsBackend), tempfileMultipartProxy),
		u.route("PUT",
			newRoute(apiProjectPattern+`\z`, "api_projects", railsBackend), tempfileMultipartProxy),

		// Group Avatar
		u.route("POST",
			newRoute(apiPattern+`v4/groups\z`, "api_groups", railsBackend), tempfileMultipartProxy),
		u.route("PUT",
			newRoute(apiPattern+`v4/groups/[^/]+\z`, "api_groups", railsBackend), tempfileMultipartProxy),

		// Organization Avatar
		u.route("POST",
			newRoute(apiPattern+`v4/organizations\z`, "api_organizations", railsBackend), tempfileMultipartProxy),
		u.route("PUT",
			newRoute(apiPattern+`v4/organizations/[0-9]+\z`, "api_organizations", railsBackend), tempfileMultipartProxy),

		// User Avatar
		u.route("PUT",
			newRoute(apiPattern+`v4/user/avatar\z`, "api_user_avatar", railsBackend), tempfileMultipartProxy),
		u.route("POST",
			newRoute(apiPattern+`v4/users\z`, "api_users", railsBackend), tempfileMultipartProxy),
		u.route("PUT",
			newRoute(apiPattern+`v4/users/[0-9]+\z`, "api_users", railsBackend), tempfileMultipartProxy),

		// GitLab Observability Backend (GOB). Write paths are versioned with v1 to align with
		// OpenTelemetry compatibility, where SDKs POST to /v1/traces, /v1/logs and /v1/metrics.
		u.route("POST", newRoute(apiProjectPattern+`/observability/v1/traces`, "api_observability_traces", railsBackend), gob.WithProjectAuth("/write/traces")),
		u.route("POST", newRoute(apiProjectPattern+`/observability/v1/logs`, "api_observability_logs", railsBackend), gob.WithProjectAuth("/write/logs")),
		u.route("POST", newRoute(apiProjectPattern+`/observability/v1/metrics`, "api_observability_metrics", railsBackend), gob.WithProjectAuth("/write/metrics")),

		u.route("GET", newRoute(apiProjectPattern+`/observability/v1/analytics`, "api_observability_analytics", railsBackend), gob.WithProjectAuth("/read/analytics")),
		u.route("GET", newRoute(apiProjectPattern+`/observability/v1/traces`, "api_observability_traces", railsBackend), gob.WithProjectAuth("/read/traces")),
		u.route("GET", newRoute(apiProjectPattern+`/observability/v1/logs`, "api_observability_logs", railsBackend), gob.WithProjectAuth("/read/logs")),
		u.route("GET", newRoute(apiProjectPattern+`/observability/v1/metrics`, "api_observability_metrics", railsBackend), gob.WithProjectAuth("/read/metrics")),
		u.route("GET", newRoute(apiProjectPattern+`/observability/v1/services`, "api_observability_services", railsBackend), gob.WithProjectAuth("/read/services")),

		// Explicitly proxy API requests
		u.route("",
			newRoute(apiPattern, "api", railsBackend), proxy),

		// Serve assets
		u.route(
			"",
			newRoute(`^/assets/`, "assets", railsBackend),
			static.ServeExisting(
				u.URLPrefix,
				staticpages.CacheExpireMax,
				assetsNotFoundHandler,
			),
			withoutTracing(), // Tracing on assets is very noisy
			withAllowOrigins("^https://.*\\.web-ide\\.gitlab-static\\.net$"),
		),

		// Uploads
		u.route("POST",
			newRoute(projectPattern+`uploads\z`, "project_uploads", railsBackend), mimeMultipartUploader),
		u.route("POST",
			newRoute(snippetUploadPattern, "personal_snippet_uploads", railsBackend), mimeMultipartUploader),
		u.route("POST",
			newRoute(userUploadPattern, "user_uploads", railsBackend), mimeMultipartUploader),

		// health checks don't intercept errors and go straight to rails
		// TODO: We should probably not return a HTML deploy page?
		//       https://gitlab.com/gitlab-org/gitlab/-/issues/336326
		u.route("",
			newRoute("^/-/(readiness|liveness)$", "liveness", selfBackend), static.DeployPage(probeUpstream)),
		u.route("",
			newRoute("^/-/health$", "health", selfBackend), static.DeployPage(healthUpstream)),

		// This route lets us filter out health checks from our metrics.
		u.route("",
			newRoute("^/-/", "dash", railsBackend), defaultUpstream),

		u.route("",
			newRoute("", "default", railsBackend), defaultUpstream),
	}

	// Routes which should actually be served locally by a Geo Proxy. If none
	// matches, then then proxy the request.
	u.geoLocalRoutes = []routeEntry{
		// Git and LFS requests
		//
		// Geo already redirects pushes, with special terminal output.
		// Excessive secondary lag can cause unexpected behavior since
		// pulls are performed against a different source of truth. Ideally, we'd
		// proxy/redirect pulls as well, when the secondary is not up-to-date.
		//
		u.route("GET",
			newRoute(geoGitProjectPattern+`info/refs\z`, "geo_git_info_refs", "gitaly"), git.GetInfoRefsHandler(api)),
		u.route("POST",
			newRoute(geoGitProjectPattern+`git-upload-pack\z`, "geo_git_upload_pack", "gitaly"), contentEncodingHandler(git.UploadPack(api)), withMatcher(isContentType("application/x-git-upload-pack-request"))),
		u.route("GET",
			newRoute(geoGitProjectPattern+`gitlab-lfs/objects/([0-9a-f]{64})\z`, "geo_git_lfs_objects", railsBackend), defaultUpstream),
		u.route("POST",
			newRoute(geoGitProjectPattern+`info/lfs/objects/batch\z`, "geo_git_lfs_info_objects_batch", railsBackend), defaultUpstream),

		// Serve health checks from this Geo secondary
		u.route("",
			newRoute("^/-/(readiness|liveness)$", "geo_liveness", selfBackend), static.DeployPage(probeUpstream)),
		u.route("",
			newRoute("^/-/health$", "geo_health", selfBackend), static.DeployPage(healthUpstream)),
		u.route("",
			newRoute("^/-/metrics$", "geo_metrics", railsBackend), defaultUpstream),

		// Authentication routes
		u.route("",
			newRoute("^/users/auth/geo/(sign_in|sign_out)$", "users_auth_geo", railsBackend), defaultUpstream),
		u.route("",
			newRoute("^/oauth/geo/(auth|callback|logout)$", "oauth_geo", railsBackend), defaultUpstream),

		// Admin Area > Geo routes
		u.route("",
			newRoute("^/admin/geo/replication/projects", "admin_geo_replication_projects", railsBackend), defaultUpstream),
		u.route("",
			newRoute("^/admin/geo/replication/designs", "admin_geo_replication_designs", railsBackend), defaultUpstream),

		// Geo API routes
		u.route("",
			newRoute("^/api/v4/geo_replication", "api_geo_replication", railsBackend), defaultUpstream),
		u.route("",
			newRoute("^/api/v4/geo/proxy_git_ssh", "api_geo_proxy_git_ssh", railsBackend), defaultUpstream),
		u.route("",
			newRoute("^/api/v4/geo/graphql", "api_geo_graphql", railsBackend), defaultUpstream),
		u.route("",
			newRoute("^/api/v4/geo_nodes/current/failures", "api_geo_nodes_current_failures", railsBackend), defaultUpstream),
		u.route("",
			newRoute("^/api/v4/geo_sites/current/failures", "api_geo_sites_current_failures", railsBackend), defaultUpstream),

		// Internal API routes
		u.route("",
			newRoute("^/api/v4/internal", "api_internal", railsBackend), defaultUpstream),

		u.route(
			"",
			newRoute(`^/assets/`, "assets", railsBackend),
			static.ServeExisting(
				u.URLPrefix,
				staticpages.CacheExpireMax,
				assetsNotFoundHandler,
			),
			withoutTracing(), // Tracing on assets is very noisy
			withAllowOrigins("^https://.*\\.web-ide\\.gitlab-static\\.net$"),
		),

		// Don't define a catch-all route. If a route does not match, then we know
		// the request should be proxied.
	}
}