mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
refactor(gitlab): replace Omnibus with the Cloud Native GitLab composition
Deploy GitLab from the official CNG CE images as six services: webservice (puma) and sidekiq scale to the topology default, workhorse is the sole HTTP entry, gitaly and a one-shot migrations job are pinned singletons, shell runs gitlab-sshd. The Omnibus container, its Dockerfile and the GITLAB_OMNIBUS_* config groups are removed. All rails/workhorse/shell/gitaly configs and shared secrets are pre-rendered by Ansible and mounted read-only; eleven credentials land in meta/schema.yml including an rsa_pem_2048 OIDC signing key (new value_generator algorithm). Postgres, redis and S3 object storage ride the existing lookups; OIDC, SMTP, the public SSH port and the root password are preserved. compose_volumes gains a per-volume nfs:false opt-out so the gitaly repositories volume stays node-local in swarm (upstream forbids git repositories on NFS); nfs_prep skips volumes without driver_opts accordingly. The Nextcloud gitlab integration execs the CNG rails runner. ruby_single_line dies with its only consumer; ruby_dq keeps test coverage. Ports (8181/8080/2222/8075/3808/9122) and service names resolve from single SPOT declarations in meta/services.yml via vars constants; the version pin sits on the webservice image pair so the updater can bump it. Live-validated in compose up to all six services healthy and migrations completed; fresh-install-only semantics for existing Omnibus data are documented. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
3ebc4113b1
commit
0bf1e8ef8c
33 files changed
+1037
-258
No files matched your search
@@ -143,6 +143,13 @@ def compose_volumes(
|
||||
optional ``name:`` field = docker volume name). Entries with
|
||||
``type: config`` / ``type: secret`` emit their respective top-level
|
||||
sections; everything else flows through the volumes section.
|
||||
|
||||
A ``type: volume`` entry may set ``nfs: false`` to opt out of the
|
||||
swarm NFS rewrite: the volume then stays a plain node-local named
|
||||
volume even when ``storage.backend`` is ``nfs`` (e.g. gitaly
|
||||
repository storage, which upstream forbids on NFS). Any other
|
||||
``nfs`` value (absent, ``true``, or the uid/gid/mode dict consumed
|
||||
by the NFS subdir pre-creation) keeps the rewrite.
|
||||
"""
|
||||
|
||||
if applications is None:
|
||||
@@ -242,6 +249,8 @@ def compose_volumes(
|
||||
docker_name = entry.get("name")
|
||||
if docker_name:
|
||||
spec["name"] = docker_name
|
||||
if "nfs" in entry:
|
||||
spec["nfs"] = entry["nfs"]
|
||||
volumes[semantic_name] = spec
|
||||
continue
|
||||
|
||||
@@ -282,8 +291,8 @@ def compose_volumes(
|
||||
for vol_name, vol_spec in list(volumes.items()):
|
||||
if not isinstance(vol_spec, dict):
|
||||
continue
|
||||
vol_spec.pop("nfs", None)
|
||||
if swarm_nfs_enabled and not role_pinned:
|
||||
nfs_opted_out = vol_spec.pop("nfs", None) is False
|
||||
if swarm_nfs_enabled and not role_pinned and not nfs_opted_out:
|
||||
named = vol_spec.get("name", vol_name)
|
||||
vol_spec.update(_swarm_nfs_driver_opts(dir_var_lib, str(named)))
|
||||
|
||||
|
||||
+1
-71
@@ -7,79 +7,15 @@
|
||||
# Output:
|
||||
# "escaped_value"
|
||||
#
|
||||
# Safe for GitLab Omnibus (gitlab.rb), Chef, Ruby DSLs.
|
||||
# Safe for Ruby DSLs (rails initializers, Chef).
|
||||
|
||||
|
||||
class FilterModule:
|
||||
def filters(self):
|
||||
return {
|
||||
"ruby_dq": self.ruby_double_quoted_string,
|
||||
"ruby_single_line": self.ruby_single_line,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def ruby_single_line(value):
|
||||
"""Collapse a multi-line Ruby program to a single physical line.
|
||||
|
||||
A swarm ``docker stack deploy`` env-file value MUST be one physical
|
||||
line; embedded newlines are read as separate (whitespace-named)
|
||||
env entries and abort the deploy. Newlines at bracket depth 0 are
|
||||
statement boundaries and become ``;``; newlines inside an open
|
||||
``(``/``[``/``{`` (or a quoted string) are continuations and become
|
||||
a space, which keeps Ruby hash/array literals valid.
|
||||
|
||||
Args:
|
||||
value: Multi-line Ruby source (e.g. GITLAB_OMNIBUS_CONFIG).
|
||||
|
||||
Returns:
|
||||
The same program as a single line.
|
||||
"""
|
||||
if value is None:
|
||||
return ""
|
||||
|
||||
s = str(value)
|
||||
out = []
|
||||
depth = 0
|
||||
in_str = False
|
||||
quote = ""
|
||||
i = 0
|
||||
n = len(s)
|
||||
while i < n:
|
||||
c = s[i]
|
||||
if in_str:
|
||||
if c == "\\" and i + 1 < n:
|
||||
out.append(c)
|
||||
out.append(s[i + 1])
|
||||
i += 2
|
||||
continue
|
||||
if c == "\n":
|
||||
# A literal newline inside a string would still corrupt the
|
||||
# single-line env-file; collapse it to a space.
|
||||
out.append(" ")
|
||||
i += 1
|
||||
continue
|
||||
out.append(c)
|
||||
if c == quote:
|
||||
in_str = False
|
||||
i += 1
|
||||
continue
|
||||
if c in ("'", '"'):
|
||||
in_str = True
|
||||
quote = c
|
||||
out.append(c)
|
||||
elif c in "([{":
|
||||
depth += 1
|
||||
out.append(c)
|
||||
elif c in ")]}":
|
||||
depth = max(0, depth - 1)
|
||||
out.append(c)
|
||||
elif c == "\n":
|
||||
out.append(";" if depth == 0 else " ")
|
||||
else:
|
||||
out.append(c)
|
||||
i += 1
|
||||
return "".join(out)
|
||||
|
||||
@staticmethod
|
||||
def ruby_double_quoted_string(value):
|
||||
"""
|
||||
@@ -104,14 +40,8 @@ class FilterModule:
|
||||
return '""'
|
||||
|
||||
s = str(value)
|
||||
|
||||
# Escape backslash first
|
||||
s = s.replace("\\", "\\\\")
|
||||
|
||||
# Escape double quotes
|
||||
s = s.replace('"', '\\"')
|
||||
|
||||
# Escape control characters
|
||||
s = s.replace("\n", "\\n")
|
||||
s = s.replace("\r", "\\r")
|
||||
s = s.replace("\t", "\\t")
|
||||
|
||||
@@ -14,4 +14,5 @@
|
||||
- (storage.backend | default('local')) == 'nfs'
|
||||
- (groups['svc-storage-nfs-server'] | default([])) | length > 0
|
||||
- item.value is mapping
|
||||
- item.value.driver_opts is defined
|
||||
- application_id not in lookup('roles_with_default_placement', 'manager')
|
||||
@@ -6,19 +6,41 @@ Accelerate your development with GitLab, an all-in-one platform for source code
|
||||
|
||||
## Overview
|
||||
|
||||
This role deploys GitLab using Docker, automating the installation, configuration, and management of your GitLab server. It integrates with an external PostgreSQL database and sets up an NGINX reverse proxy, streamlining your GitLab deployment through efficient container orchestration and customizable Ansible variables.
|
||||
This role deploys GitLab from the official Cloud Native GitLab (CNG) CE images at `registry.gitlab.com/gitlab-org/build/cng/` as separate services: `webservice` (puma), `sidekiq`, `workhorse` (sole HTTP entry point), `gitaly`, `shell` (gitlab-sshd) and a one-shot `migrations` job. All images share a single version pin (`services.webservice.version`). PostgreSQL, Redis and S3-compatible object storage are wired through the platform lookups (central or sidecar), OIDC and SMTP through the platform SSO and email services. The front proxy terminates TLS and forwards HTTP to workhorse.
|
||||
|
||||
## Features
|
||||
|
||||
- **GitLab Installation:** Automatically deploys GitLab within a Docker container.
|
||||
- **External PostgreSQL Database:** Configures GitLab to connect to an external PostgreSQL database.
|
||||
- **NGINX Reverse Proxy Integration:** Simplifies secure access with an NGINX reverse proxy.
|
||||
- **Customizable Configuration:** Easily tailor deployment settings using Ansible variables and templates.
|
||||
- **CNG multi-service deployment:** webservice, sidekiq, workhorse, gitaly, gitlab-shell and a migrations one-shot from unmodified upstream images.
|
||||
- **External/central PostgreSQL and Redis:** rails `database.yml`, `resque.yml`, `cable.yml` and the workhorse config are pre-rendered by Ansible and mounted read-only.
|
||||
- **Consolidated object storage:** artifacts, LFS, uploads, packages, external diffs, dependency proxy, terraform state, CI secure files and pages buckets on any S3-compatible endpoint; named volumes (`gitlab_shared`, `gitlab_uploads`, `gitlab_builds`) carry the data when object storage is disabled.
|
||||
- **OIDC single sign-on and SMTP:** rendered into `gitlab.yml` and an `smtp_settings.rb` initializer.
|
||||
- **Git over SSH:** gitlab-sshd on the public SSH port with role-generated host keys under `<instance>/config/hostkeys/`. Back up that directory: it is not part of any named volume, and a host rebuild or instance purge regenerates the keys, so every git client then sees a host-key-changed warning until it re-trusts the new key.
|
||||
|
||||
## Fresh installs only
|
||||
|
||||
The role provisions new GitLab instances. Volumes, secrets and backups of a pre-CNG Omnibus deployment (`gitlab_config`, `gitlab_data`, `/etc/gitlab/gitlab-secrets.json`) are not migrated or restorable into the CNG layout; deploy against a fresh database and empty volumes.
|
||||
|
||||
## Gitaly data locality
|
||||
|
||||
The `gitlab_repositories` volume is declared `nfs: false`, so in swarm mode it stays a plain node-local volume instead of being rewritten to the shared NFS backend. Gitaly is pinned to a single replica. When a swarm reschedule moves the gitaly task to another node, the repositories stay on the previous node's local volume; move the volume data manually before rescheduling gitaly.
|
||||
|
||||
## Upgrades
|
||||
|
||||
On each version bump of `services.webservice.version`:
|
||||
|
||||
1. Follow the upstream upgrade path stops between the old and new version; the `migrations` one-shot fails hard on skipped stops.
|
||||
2. Diff the CNG repo `dev/` config templates (`webservice-config`, `sidekiq-config`, `workhorse-config`, `shell-config`, `gitaly-config`) between the two tags and mirror schema changes into `templates/config/`.
|
||||
|
||||
## Omissions
|
||||
|
||||
- `kas` (Kubernetes agent server, workspaces), `pages`, `registry` and `mailroom` are not deployed; `gitlab.yml` disables them.
|
||||
- Images are the CE edition (`gitlab-webservice-ce`, `gitlab-sidekiq-ce`, `gitlab-workhorse-ce`, `gitlab-rails-ce`); switch the image keys in `meta/services.yml` to the `-ee` variants for an EE deployment.
|
||||
|
||||
## Further Resources
|
||||
|
||||
- [GitLab Official Website](https://about.gitlab.com/)
|
||||
- [Running GitLab on Docker](https://ralph.blog.imixs.com/2019/06/09/running-gitlab-on-docker/)
|
||||
- [Cloud Native GitLab (CNG) images](https://gitlab.com/gitlab-org/build/CNG)
|
||||
- [GitLab Helm charts documentation](https://docs.gitlab.com/charts/)
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
ARG GITLAB_IMAGE
|
||||
ARG GITLAB_VERSION
|
||||
FROM ${GITLAB_IMAGE}:${GITLAB_VERSION}
|
||||
|
||||
RUN sed -i '1i require_relative "../redis"' \
|
||||
/opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/instrumentation/redis.rb
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
credentials:
|
||||
secret_key_base:
|
||||
description: Rails secret key base used to verify signed cookies and tokens
|
||||
algorithm: random_hex
|
||||
validation: "^[a-f0-9]{128}$"
|
||||
otp_key_base:
|
||||
description: Key base encrypting two-factor authentication secrets
|
||||
algorithm: random_hex
|
||||
validation: "^[a-f0-9]{128}$"
|
||||
db_key_base:
|
||||
description: Key base encrypting database columns (CI variables, tokens)
|
||||
algorithm: random_hex
|
||||
validation: "^[a-f0-9]{128}$"
|
||||
encrypted_settings_key_base:
|
||||
description: Key base encrypting settings files
|
||||
algorithm: random_hex
|
||||
validation: "^[a-f0-9]{128}$"
|
||||
active_record_encryption_primary_key:
|
||||
description: Primary key for Active Record encrypted columns
|
||||
algorithm: random_hex_16
|
||||
validation: "^[a-f0-9]{32}$"
|
||||
active_record_encryption_deterministic_key:
|
||||
description: Deterministic key for Active Record encrypted columns
|
||||
algorithm: random_hex_16
|
||||
validation: "^[a-f0-9]{32}$"
|
||||
active_record_encryption_key_derivation_salt:
|
||||
description: Key derivation salt for Active Record encryption
|
||||
algorithm: random_hex_16
|
||||
validation: "^[a-f0-9]{32}$"
|
||||
gitlab_shell_secret:
|
||||
description: Shared token authenticating gitlab-shell and gitaly against the rails API
|
||||
algorithm: alphanumeric
|
||||
validation: "^[A-Za-z0-9]{64}$"
|
||||
gitaly_token:
|
||||
description: Auth token shared between rails and the gitaly gRPC listener
|
||||
algorithm: alphanumeric
|
||||
validation: "^[A-Za-z0-9]{64}$"
|
||||
workhorse_secret:
|
||||
description: 32-byte value shared between workhorse and rails, mounted base64-encoded
|
||||
algorithm: random_hex_16
|
||||
validation: "^[a-f0-9]{32}$"
|
||||
openid_connect_signing_key:
|
||||
description: RSA private key signing GitLab's own OpenID Connect provider tokens
|
||||
algorithm: rsa_pem_2048
|
||||
validation: "^-----BEGIN RSA PRIVATE KEY-----[\\s\\S]+-----END RSA PRIVATE KEY-----\\s*$"
|
||||
@@ -41,29 +41,86 @@ gitlab:
|
||||
bond: 1
|
||||
enabled: true
|
||||
shared: true
|
||||
image: gitlab/gitlab-ee
|
||||
version: 19.1.1-ee.0
|
||||
name: gitlab
|
||||
min_storage: 30GB
|
||||
cpus: "4.0"
|
||||
mem_reservation: 8g
|
||||
mem_limit: 12g
|
||||
pids_limit: 4096
|
||||
# Exception: the entity runs no container of its own (webservice/sidekiq/workhorse/gitaly/shell/migrations carry the real budgets); these keys only satisfy the entity resource contract
|
||||
cpus: "0.1"
|
||||
mem_reservation: 128m
|
||||
mem_limit: 256m
|
||||
pids_limit: 128
|
||||
ports:
|
||||
local:
|
||||
http: 8024
|
||||
public:
|
||||
ssh: 2202
|
||||
internal:
|
||||
http: 80
|
||||
ssh: 22
|
||||
http: 8181
|
||||
ssh: 2222
|
||||
proxy:
|
||||
service_key: gitlab
|
||||
service_key: workhorse
|
||||
run_after:
|
||||
- web-app-matomo
|
||||
- web-app-keycloak
|
||||
- web-app-mailu
|
||||
lifecycle: beta
|
||||
webservice:
|
||||
image: registry.gitlab.com/gitlab-org/build/cng/gitlab-webservice-ce
|
||||
version: v19.1.1
|
||||
name: gitlab-webservice
|
||||
ports:
|
||||
internal:
|
||||
http: 8080
|
||||
cpus: "2.0"
|
||||
mem_reservation: 4g
|
||||
mem_limit: 6g
|
||||
pids_limit: 2048
|
||||
sidekiq:
|
||||
image: registry.gitlab.com/gitlab-org/build/cng/gitlab-sidekiq-ce
|
||||
name: gitlab-sidekiq
|
||||
cpus: "1.0"
|
||||
mem_reservation: 2g
|
||||
mem_limit: 3g
|
||||
pids_limit: 1024
|
||||
ports:
|
||||
internal:
|
||||
http: 3808
|
||||
workhorse:
|
||||
image: registry.gitlab.com/gitlab-org/build/cng/gitlab-workhorse-ce
|
||||
name: gitlab-workhorse
|
||||
cpus: "0.5"
|
||||
mem_reservation: 256m
|
||||
mem_limit: 512m
|
||||
pids_limit: 256
|
||||
gitaly:
|
||||
# nocheck: single-replica
|
||||
replicas: 1
|
||||
image: registry.gitlab.com/gitlab-org/build/cng/gitaly
|
||||
name: gitlab-gitaly
|
||||
ports:
|
||||
internal:
|
||||
grpc: 8075
|
||||
cpus: "1.0"
|
||||
mem_reservation: 1g
|
||||
mem_limit: 2g
|
||||
pids_limit: 1024
|
||||
shell:
|
||||
image: registry.gitlab.com/gitlab-org/build/cng/gitlab-shell
|
||||
name: gitlab-shell
|
||||
ports:
|
||||
internal:
|
||||
metrics: 9122
|
||||
cpus: "0.25"
|
||||
mem_reservation: 128m
|
||||
mem_limit: 256m
|
||||
pids_limit: 256
|
||||
migrations:
|
||||
# nocheck: single-replica
|
||||
replicas: 1
|
||||
image: registry.gitlab.com/gitlab-org/build/cng/gitlab-rails-ce
|
||||
name: gitlab-migrations
|
||||
cpus: "1.0"
|
||||
mem_reservation: 1g
|
||||
mem_limit: 2g
|
||||
pids_limit: 512
|
||||
seaweedfs:
|
||||
bond: 1
|
||||
enabled: "{{ 'web-app-seaweedfs' in group_names }}"
|
||||
|
||||
@@ -1,19 +1,114 @@
|
||||
---
|
||||
config:
|
||||
# Exception: nfs false keeps gitaly repository storage node-local in swarm; upstream forbids git repositories on NFS
|
||||
repositories:
|
||||
type: volume
|
||||
name: gitlab_config
|
||||
name: gitlab_repositories
|
||||
nfs: false
|
||||
mounts:
|
||||
- service: gitlab
|
||||
target: /etc/gitlab
|
||||
logs:
|
||||
- service: gitaly
|
||||
target: /home/git/repositories
|
||||
shared:
|
||||
type: volume
|
||||
name: gitlab_logs
|
||||
name: gitlab_shared
|
||||
mounts:
|
||||
- service: gitlab
|
||||
target: /var/log/gitlab
|
||||
data:
|
||||
- service: webservice
|
||||
target: /srv/gitlab/shared
|
||||
- service: sidekiq
|
||||
target: /srv/gitlab/shared
|
||||
- service: workhorse
|
||||
target: /srv/gitlab/shared
|
||||
uploads:
|
||||
type: volume
|
||||
name: gitlab_data
|
||||
name: gitlab_uploads
|
||||
mounts:
|
||||
- service: gitlab
|
||||
target: /var/opt/gitlab
|
||||
- service: webservice
|
||||
target: /srv/gitlab/public/uploads
|
||||
- service: sidekiq
|
||||
target: /srv/gitlab/public/uploads
|
||||
- service: workhorse
|
||||
target: /srv/gitlab/public/uploads
|
||||
builds:
|
||||
type: volume
|
||||
name: gitlab_builds
|
||||
mounts:
|
||||
- service: webservice
|
||||
target: /srv/gitlab/builds
|
||||
- service: sidekiq
|
||||
target: /srv/gitlab/builds
|
||||
rails_templates:
|
||||
type: bind
|
||||
source: "{{ [lookup('container', 'web-app-gitlab', 'directories.config'), 'rails'] | path_join }}"
|
||||
read_only: true
|
||||
mounts:
|
||||
- service: webservice
|
||||
target: /var/opt/gitlab/config/templates
|
||||
- service: sidekiq
|
||||
target: /var/opt/gitlab/config/templates
|
||||
- service: migrations
|
||||
target: /var/opt/gitlab/config/templates
|
||||
rails_secrets:
|
||||
type: bind
|
||||
source: "{{ [lookup('container', 'web-app-gitlab', 'directories.config'), 'secrets'] | path_join }}"
|
||||
read_only: true
|
||||
mounts:
|
||||
- service: webservice
|
||||
target: /var/opt/gitlab/config/secrets
|
||||
- service: sidekiq
|
||||
target: /var/opt/gitlab/config/secrets
|
||||
- service: migrations
|
||||
target: /var/opt/gitlab/config/secrets
|
||||
workhorse_templates:
|
||||
type: bind
|
||||
source: "{{ [lookup('container', 'web-app-gitlab', 'directories.config'), 'workhorse'] | path_join }}"
|
||||
read_only: true
|
||||
mounts:
|
||||
- service: workhorse
|
||||
target: /var/opt/gitlab/config/templates
|
||||
workhorse_secret:
|
||||
type: bind
|
||||
source: "{{ [lookup('container', 'web-app-gitlab', 'directories.config'), 'secrets', '.gitlab_workhorse_secret'] | path_join }}"
|
||||
read_only: true
|
||||
mounts:
|
||||
- service: workhorse
|
||||
target: /etc/gitlab/gitlab-workhorse/secret
|
||||
- service: webservice
|
||||
target: /srv/gitlab/.gitlab_workhorse_secret
|
||||
shell_templates:
|
||||
type: bind
|
||||
source: "{{ [lookup('container', 'web-app-gitlab', 'directories.config'), 'shell'] | path_join }}"
|
||||
read_only: true
|
||||
mounts:
|
||||
- service: shell
|
||||
target: /srv/gitlab-config
|
||||
shell_secret:
|
||||
type: bind
|
||||
source: "{{ [lookup('container', 'web-app-gitlab', 'directories.config'), 'secrets', '.gitlab_shell_secret'] | path_join }}"
|
||||
read_only: true
|
||||
mounts:
|
||||
- service: shell
|
||||
target: /srv/gitlab-secrets/.gitlab_shell_secret
|
||||
gitaly_config:
|
||||
type: bind
|
||||
source: "{{ [lookup('container', 'web-app-gitlab', 'directories.config'), 'gitaly'] | path_join }}"
|
||||
read_only: true
|
||||
mounts:
|
||||
- service: gitaly
|
||||
target: /etc/gitaly
|
||||
hostkeys:
|
||||
type: bind
|
||||
source: "{{ [lookup('container', 'web-app-gitlab', 'directories.config'), 'hostkeys'] | path_join }}"
|
||||
read_only: true
|
||||
mounts:
|
||||
- service: shell
|
||||
target: /srv/ssh-hostkeys
|
||||
smtp_initializer:
|
||||
type: bind
|
||||
source: "{{ [lookup('container', 'web-app-gitlab', 'directories.config'), 'smtp_settings.rb'] | path_join }}"
|
||||
read_only: true
|
||||
mounts:
|
||||
- service: webservice
|
||||
target: /srv/gitlab/config/initializers/smtp_settings.rb
|
||||
when: "{{ lookup('email', 'web-app-gitlab').enabled | bool }}"
|
||||
- service: sidekiq
|
||||
target: /srv/gitlab/config/initializers/smtp_settings.rb
|
||||
when: "{{ lookup('email', 'web-app-gitlab').enabled | bool }}"
|
||||
@@ -1,6 +1,24 @@
|
||||
---
|
||||
- include_tasks: utils/once/flag.yml
|
||||
|
||||
- name: "🧩 load docker, db and proxy for {{ application_id }}"
|
||||
- name: "📂 Include setup for domain '{{ lookup('domain', application_id) }}'"
|
||||
include_role:
|
||||
name: sys-stk-full
|
||||
name: sys-stk-front-proxy
|
||||
vars:
|
||||
domain: "{{ lookup('domain', application_id) }}"
|
||||
http_port: "{{ lookup('config', application_id, 'services.gitlab.ports.local.http') }}"
|
||||
|
||||
- name: "🧩 load docker and db for {{ application_id }}"
|
||||
include_role:
|
||||
name: sys-stk-backend
|
||||
vars:
|
||||
compose_handlers_flush: false
|
||||
|
||||
- name: "📄 Render GitLab CNG configuration"
|
||||
include_tasks: 02_config.yml
|
||||
|
||||
- name: 🚿 Flush handlers to start the GitLab stack
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "⏳ Wait for GitLab services"
|
||||
include_tasks: 03_wait.yml
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
- name: "📁 Ensure GitLab config directories for '{{ application_id }}'"
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ GITLAB_CONFIG_RAILS_DIR }}"
|
||||
- "{{ GITLAB_CONFIG_SECRETS_DIR }}"
|
||||
- "{{ GITLAB_CONFIG_WORKHORSE_DIR }}"
|
||||
- "{{ GITLAB_CONFIG_SHELL_DIR }}"
|
||||
- "{{ GITLAB_CONFIG_GITALY_DIR }}"
|
||||
- "{{ GITLAB_CONFIG_HOSTKEYS_DIR }}"
|
||||
|
||||
- name: "📄 Render GitLab CNG config files (local + swarm peers)"
|
||||
ansible.builtin.include_tasks:
|
||||
file: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/render_replicated_templates.yml') }}"
|
||||
vars:
|
||||
role_templates:
|
||||
- src: "config/gitlab.yml.j2"
|
||||
dest: "{{ [GITLAB_CONFIG_RAILS_DIR, 'gitlab.yml'] | path_join }}"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0440"
|
||||
- src: "config/database.yml.j2"
|
||||
dest: "{{ [GITLAB_CONFIG_RAILS_DIR, 'database.yml'] | path_join }}"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0440"
|
||||
- src: "config/resque.yml.j2"
|
||||
dest: "{{ [GITLAB_CONFIG_RAILS_DIR, 'resque.yml'] | path_join }}"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0440"
|
||||
- src: "config/cable.yml.j2"
|
||||
dest: "{{ [GITLAB_CONFIG_RAILS_DIR, 'cable.yml'] | path_join }}"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0440"
|
||||
- src: "config/secrets.yml.j2"
|
||||
dest: "{{ [GITLAB_CONFIG_RAILS_DIR, 'secrets.yml'] | path_join }}"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0400"
|
||||
- src: "config/sidekiq_queues.yml.j2"
|
||||
dest: "{{ [GITLAB_CONFIG_RAILS_DIR, 'sidekiq_queues.yml'] | path_join }}"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0444"
|
||||
- src: "config/smtp_settings.rb.j2"
|
||||
dest: "{{ [GITLAB_CONFIG_DIR, 'smtp_settings.rb'] | path_join }}"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0440"
|
||||
condition: "{{ GITLAB_EMAIL_ENABLED | bool }}"
|
||||
- src: "config/gitlab_shell_secret.j2"
|
||||
dest: "{{ [GITLAB_CONFIG_SECRETS_DIR, '.gitlab_shell_secret'] | path_join }}"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0400"
|
||||
- src: "config/gitlab_shell_secret.j2"
|
||||
dest: "{{ [GITLAB_CONFIG_DIR, 'gitaly', '.gitlab_shell_secret'] | path_join }}"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0400"
|
||||
- src: "config/workhorse_secret.j2"
|
||||
dest: "{{ [GITLAB_CONFIG_SECRETS_DIR, '.gitlab_workhorse_secret'] | path_join }}"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0400"
|
||||
- src: "config/workhorse-config.toml.j2"
|
||||
dest: "{{ [GITLAB_CONFIG_WORKHORSE_DIR, 'workhorse-config.toml'] | path_join }}"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0440"
|
||||
- src: "config/shell-config.yml.j2"
|
||||
dest: "{{ [GITLAB_CONFIG_SHELL_DIR, 'config.yml'] | path_join }}"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0444"
|
||||
- src: "config/gitaly-config.toml.j2"
|
||||
dest: "{{ [GITLAB_CONFIG_GITALY_DIR, 'config.toml'] | path_join }}"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0440"
|
||||
|
||||
- name: "🔑 Generate GitLab SSH host keys (on stack host)"
|
||||
community.crypto.openssh_keypair:
|
||||
path: "{{ [GITLAB_CONFIG_HOSTKEYS_DIR, item.name] | path_join }}"
|
||||
type: "{{ item.type }}"
|
||||
size: "{{ item.size | default(omit) }}"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0400"
|
||||
loop:
|
||||
- { name: ssh_host_rsa_key, type: rsa, size: 4096 }
|
||||
- { name: ssh_host_ed25519_key, type: ed25519 }
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
delegate_to: "{{ STACK_HOST }}"
|
||||
run_once: true
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
notify: compose up
|
||||
|
||||
- name: "[swarm] Slurp GitLab SSH host keys from stack host"
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ [GITLAB_CONFIG_HOSTKEYS_DIR, item] | path_join }}"
|
||||
loop:
|
||||
- ssh_host_rsa_key
|
||||
- ssh_host_ed25519_key
|
||||
delegate_to: "{{ STACK_HOST }}"
|
||||
run_once: true
|
||||
register: gitlab_hostkeys_slurp
|
||||
when: DEPLOYMENT_MODE == 'swarm'
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "[swarm] Distribute GitLab SSH host keys to swarm peers"
|
||||
ansible.builtin.copy:
|
||||
content: "{{ item.content | b64decode }}"
|
||||
dest: "{{ [GITLAB_CONFIG_HOSTKEYS_DIR, item.item] | path_join }}"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0400"
|
||||
loop: "{{ gitlab_hostkeys_slurp.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when:
|
||||
- DEPLOYMENT_MODE == 'swarm'
|
||||
- not (IS_STACK_HOST | bool)
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
notify: compose up
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
- name: "🔎 Wait until '{{ item }}' container is healthy"
|
||||
ansible.builtin.include_tasks: 03a_wait_one.yml
|
||||
loop:
|
||||
- "{{ GITLAB_WEBSERVICE_SERVICE_NAME }}"
|
||||
- "{{ GITLAB_WORKHORSE_SERVICE_NAME }}"
|
||||
- "{{ GITLAB_SIDEKIQ_SERVICE_NAME }}"
|
||||
- "{{ GITLAB_GITALY_SERVICE_NAME }}"
|
||||
- "{{ GITLAB_SHELL_SERVICE_NAME }}"
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
- name: "🔍 Resolve '{{ item }}' hosting node + container id"
|
||||
ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/swarm/resolve_host_cid.yml') }}"
|
||||
vars:
|
||||
resolve_service_key: "{{ item }}"
|
||||
resolve_output_prefix: GITLAB_WAIT
|
||||
resolve_compose_cid: "{{ lookup('container_address', application_id, item) }}"
|
||||
|
||||
- name: "🔎 Check health status of '{{ item }}' container"
|
||||
ansible.builtin.shell: >
|
||||
container inspect --format '{{ "{{.State.Health.Status}}" }}'
|
||||
{{ GITLAB_WAIT_LOCAL_CID | quote }}
|
||||
register: healthcheck
|
||||
retries: 90
|
||||
delay: 10
|
||||
until: healthcheck.rc == 0 and (healthcheck.stdout | trim) == "healthy"
|
||||
changed_when: false
|
||||
delegate_to: "{{ GITLAB_WAIT_HOST_NODE }}"
|
||||
@@ -1,23 +1,117 @@
|
||||
{% include 'roles/sys-svc-compose/templates/base.yml.j2' %}
|
||||
{% set service_name = 'gitlab' %}
|
||||
{% set service_name = GITLAB_WEBSERVICE_SERVICE_NAME %}
|
||||
{{ service_name }}:
|
||||
{{ lookup('container_image', application_id, 'gitlab', custom=True) }}
|
||||
hostname: '{{ GITLAB_HOSTNAME }}'
|
||||
{{ lookup('compose_only', 'container_name', GITLAB_CONTAINER) }}
|
||||
{{ lookup('compose_only', 'pull_policy', 'never') }}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
GITLAB_IMAGE: "{{ GITLAB_IMAGE }}"
|
||||
GITLAB_VERSION: "{{ GITLAB_VERSION }}"
|
||||
{{ lookup('compose_only', 'container_name', GITLAB_WEBSERVICE_CONTAINER) }}
|
||||
{{ lookup('container_image', application_id, service_name, version=GITLAB_VERSION) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
{{ lookup('container_ports',
|
||||
['gitlab', 'http', DOCKER_BIND_HOST],
|
||||
['gitlab', 'ssh']) | indent(4) }}
|
||||
{{ lookup('container_volumes', application_id, 'gitlab') | indent(4) }}
|
||||
shm_size: '256m'
|
||||
environment:
|
||||
CONFIG_TEMPLATE_DIRECTORY: "/var/opt/gitlab/config/templates"
|
||||
CONFIG_DIRECTORY: "/srv/gitlab/config"
|
||||
GITLAB_WEBSERVER: "PUMA"
|
||||
ACTION_CABLE_IN_APP: "true"
|
||||
command: ["/scripts/wait-for-deps", "/scripts/process-wrapper"]
|
||||
healthcheck:
|
||||
test: ["CMD", "/scripts/healthcheck"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 600s
|
||||
{{ lookup('container_depends_on', application_id, extra={
|
||||
GITLAB_MIGRATIONS_SERVICE_NAME: {'condition': 'service_completed_successfully'},
|
||||
}) | indent(4) }}
|
||||
{{ lookup('container_volumes', application_id, service_name) | indent(4) }}
|
||||
{{ lookup('container_networks') | indent(4) }}
|
||||
|
||||
{% set service_name = GITLAB_WORKHORSE_SERVICE_NAME %}
|
||||
{{ service_name }}:
|
||||
{{ lookup('compose_only', 'container_name', GITLAB_WORKHORSE_CONTAINER) }}
|
||||
{{ lookup('container_image', application_id, service_name, version=GITLAB_VERSION) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
environment:
|
||||
CONFIG_TEMPLATE_DIRECTORY: "/var/opt/gitlab/config/templates"
|
||||
CONFIG_DIRECTORY: "/srv/gitlab/config"
|
||||
GITLAB_WORKHORSE_AUTH_BACKEND: "http://{{ GITLAB_WEBSERVICE_SERVICE_NAME }}:{{ GITLAB_WEBSERVICE_PORT }}"
|
||||
GITLAB_WORKHORSE_EXTRA_ARGS: "-cableBackend http://{{ GITLAB_WEBSERVICE_SERVICE_NAME }}:{{ GITLAB_WEBSERVICE_PORT }}"
|
||||
command: ["/scripts/start-workhorse"]
|
||||
healthcheck:
|
||||
test: ["CMD", "/scripts/healthcheck"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 120s
|
||||
{{ lookup('container_ports', ['gitlab', 'http', DOCKER_BIND_HOST]) | indent(4) }}
|
||||
{{ lookup('container_depends_on', application_id, extra={
|
||||
GITLAB_WEBSERVICE_SERVICE_NAME: {'condition': 'service_started'},
|
||||
}) | indent(4) }}
|
||||
{{ lookup('container_volumes', application_id, service_name) | indent(4) }}
|
||||
{{ lookup('container_networks') | indent(4) }}
|
||||
|
||||
{% set service_name = GITLAB_SIDEKIQ_SERVICE_NAME %}
|
||||
{% set container_healthcheck = 'liveness' %}
|
||||
{% set container_healthcheck_start_period = '300s' %}
|
||||
{{ service_name }}:
|
||||
{{ lookup('compose_only', 'container_name', GITLAB_SIDEKIQ_CONTAINER) }}
|
||||
{{ lookup('container_image', application_id, service_name, version=GITLAB_VERSION) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
environment:
|
||||
CONFIG_TEMPLATE_DIRECTORY: "/var/opt/gitlab/config/templates"
|
||||
CONFIG_DIRECTORY: "/srv/gitlab/config"
|
||||
command: ["/scripts/wait-for-deps", "/scripts/process-wrapper"]
|
||||
{% include 'roles/sys-svc-container/templates/healthcheck/tcp.yml.j2' %}
|
||||
{{ lookup('container_depends_on', application_id, extra={
|
||||
GITLAB_MIGRATIONS_SERVICE_NAME: {'condition': 'service_completed_successfully'},
|
||||
}) | indent(4) }}
|
||||
{{ lookup('container_volumes', application_id, service_name) | indent(4) }}
|
||||
{{ lookup('container_networks') | indent(4) }}
|
||||
|
||||
{% set service_name = GITLAB_GITALY_SERVICE_NAME %}
|
||||
{{ service_name }}:
|
||||
{{ lookup('compose_only', 'container_name', GITLAB_GITALY_CONTAINER) }}
|
||||
{{ lookup('container_image', application_id, service_name, version=GITLAB_VERSION) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
healthcheck:
|
||||
test: ["CMD", "/scripts/healthcheck"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
{{ lookup('container_depends_on', application_id) | indent(4) }}
|
||||
{{ lookup('container_volumes', application_id, service_name) | indent(4) }}
|
||||
{{ lookup('container_networks') | indent(4) }}
|
||||
|
||||
{% set service_name = GITLAB_SHELL_SERVICE_NAME %}
|
||||
{{ service_name }}:
|
||||
{{ lookup('compose_only', 'container_name', GITLAB_SHELL_CONTAINER) }}
|
||||
{{ lookup('container_image', application_id, service_name, version=GITLAB_VERSION) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
environment:
|
||||
CONFIG_TEMPLATE_DIRECTORY: "/srv/gitlab-config"
|
||||
CONFIG_DIRECTORY: "/srv/gitlab-shell"
|
||||
SSH_DAEMON: "gitlab-sshd"
|
||||
healthcheck:
|
||||
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/{{ GITLAB_SSHD_PORT }}"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
{{ lookup('container_ports', ['gitlab', 'ssh']) | indent(4) }}
|
||||
{{ lookup('container_depends_on', application_id) | indent(4) }}
|
||||
{{ lookup('container_volumes', application_id, service_name) | indent(4) }}
|
||||
{{ lookup('container_networks') | indent(4) }}
|
||||
|
||||
{% set service_name = GITLAB_MIGRATIONS_SERVICE_NAME %}
|
||||
{% set docker_restart_policy = 'on-failure' %}
|
||||
{{ service_name }}:
|
||||
{{ lookup('compose_only', 'container_name', GITLAB_MIGRATIONS_CONTAINER) }}
|
||||
{{ lookup('container_image', application_id, service_name, version=GITLAB_VERSION) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
environment:
|
||||
CONFIG_TEMPLATE_DIRECTORY: "/var/opt/gitlab/config/templates"
|
||||
CONFIG_DIRECTORY: "/srv/gitlab/config"
|
||||
BYPASS_SCHEMA_VERSION: "true"
|
||||
command: ["/scripts/wait-for-deps", "/scripts/db-migrate"]
|
||||
{{ lookup('container_depends_on', application_id) | indent(4) }}
|
||||
{{ lookup('container_volumes', application_id, service_name) | indent(4) }}
|
||||
{{ lookup('container_networks') | indent(4) }}
|
||||
|
||||
{{ lookup('compose_volumes', application_id) }}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
production:
|
||||
adapter: redis
|
||||
url: {{ GITLAB_REDIS_URL | to_json }}
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
production:
|
||||
main:
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
database: {{ lookup('database', application_id, 'name') | to_json }}
|
||||
username: {{ lookup('database', application_id, 'username') | to_json }}
|
||||
password: {{ lookup('database', application_id, 'password') | to_json }}
|
||||
host: {{ lookup('database', application_id, 'host') | to_json }}
|
||||
port: {{ lookup('database', application_id, 'port') }}
|
||||
pool: {{ POSTGRES_ALLOWED_AVG_CONNECTIONS }}
|
||||
ci:
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
database: {{ lookup('database', application_id, 'name') | to_json }}
|
||||
username: {{ lookup('database', application_id, 'username') | to_json }}
|
||||
password: {{ lookup('database', application_id, 'password') | to_json }}
|
||||
host: {{ lookup('database', application_id, 'host') | to_json }}
|
||||
port: {{ lookup('database', application_id, 'port') }}
|
||||
pool: {{ POSTGRES_ALLOWED_AVG_CONNECTIONS }}
|
||||
database_tasks: false
|
||||
@@ -0,0 +1,21 @@
|
||||
bin_dir = "/usr/local/bin"
|
||||
runtime_dir = "/home/git"
|
||||
listen_addr = "0.0.0.0:{{ GITLAB_GITALY_PORT }}"
|
||||
|
||||
[[storage]]
|
||||
name = "default"
|
||||
path = "/home/git/repositories"
|
||||
|
||||
[auth]
|
||||
token = {{ GITLAB_GITALY_TOKEN | to_json }}
|
||||
|
||||
[logging]
|
||||
format = "json"
|
||||
level = "info"
|
||||
|
||||
[gitlab]
|
||||
secret_file = "/etc/gitaly/.gitlab_shell_secret"
|
||||
url = "http://{{ GITLAB_WEBSERVICE_SERVICE_NAME }}:{{ GITLAB_WEBSERVICE_PORT }}/"
|
||||
|
||||
[hooks]
|
||||
custom_hooks_dir = "/home/git/custom_hooks"
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
production:
|
||||
gitlab:
|
||||
host: {{ GITLAB_HOSTNAME | to_json }}
|
||||
port: {{ GITLAB_WEB_PORT }}
|
||||
https: {{ GITLAB_TLS_ENABLED | bool | string | lower }}
|
||||
email_enabled: {{ GITLAB_EMAIL_ENABLED | bool | string | lower }}
|
||||
{% if GITLAB_EMAIL_ENABLED %}
|
||||
email_from: {{ GITLAB_EMAIL['from'] | to_json }}
|
||||
email_display_name: "GitLab"
|
||||
email_reply_to: {{ GITLAB_EMAIL['from'] | to_json }}
|
||||
{% endif %}
|
||||
{% if GITLAB_OBJSTORE_ENABLED %}
|
||||
object_store:
|
||||
enabled: true
|
||||
proxy_download: true
|
||||
connection:
|
||||
provider: "AWS"
|
||||
aws_access_key_id: {{ GITLAB_OBJSTORE_ACCESS_KEY | to_json }}
|
||||
aws_secret_access_key: {{ GITLAB_OBJSTORE_SECRET_KEY | to_json }}
|
||||
region: {{ GITLAB_OBJSTORE_REGION | to_json }}
|
||||
endpoint: {{ GITLAB_OBJSTORE_ENDPOINT | to_json }}
|
||||
path_style: true
|
||||
objects:
|
||||
artifacts:
|
||||
bucket: {{ (GITLAB_OBJSTORE_BUCKET ~ '/artifacts') | to_json }}
|
||||
external_diffs:
|
||||
bucket: {{ (GITLAB_OBJSTORE_BUCKET ~ '/external-diffs') | to_json }}
|
||||
lfs:
|
||||
bucket: {{ (GITLAB_OBJSTORE_BUCKET ~ '/lfs') | to_json }}
|
||||
uploads:
|
||||
bucket: {{ (GITLAB_OBJSTORE_BUCKET ~ '/uploads') | to_json }}
|
||||
packages:
|
||||
bucket: {{ (GITLAB_OBJSTORE_BUCKET ~ '/packages') | to_json }}
|
||||
dependency_proxy:
|
||||
bucket: {{ (GITLAB_OBJSTORE_BUCKET ~ '/dependency-proxy') | to_json }}
|
||||
terraform_state:
|
||||
bucket: {{ (GITLAB_OBJSTORE_BUCKET ~ '/terraform-state') | to_json }}
|
||||
ci_secure_files:
|
||||
bucket: {{ (GITLAB_OBJSTORE_BUCKET ~ '/ci-secure-files') | to_json }}
|
||||
pages:
|
||||
bucket: {{ (GITLAB_OBJSTORE_BUCKET ~ '/pages') | to_json }}
|
||||
{% else %}
|
||||
object_store:
|
||||
enabled: false
|
||||
{% endif %}
|
||||
incoming_email:
|
||||
enabled: false
|
||||
artifacts:
|
||||
enabled: true
|
||||
lfs:
|
||||
enabled: true
|
||||
pages:
|
||||
enabled: false
|
||||
mattermost:
|
||||
enabled: false
|
||||
registry:
|
||||
enabled: false
|
||||
ldap:
|
||||
enabled: false
|
||||
kerberos:
|
||||
enabled: false
|
||||
{% if GITLAB_OIDC_ENABLED %}
|
||||
omniauth:
|
||||
enabled: true
|
||||
allow_single_sign_on: ["openid_connect"]
|
||||
block_auto_created_users: false
|
||||
auto_link_user: ["openid_connect"]
|
||||
providers:
|
||||
- name: "openid_connect"
|
||||
label: {{ GITLAB_OIDC_LABEL | to_json }}
|
||||
args:
|
||||
name: "openid_connect"
|
||||
scope:
|
||||
- "openid"
|
||||
- "profile"
|
||||
- "email"
|
||||
response_type: "code"
|
||||
issuer: {{ GITLAB_OIDC_ISSUER | to_json }}
|
||||
discovery: true
|
||||
uid_field: {{ GITLAB_OIDC_UID_FIELD | to_json }}
|
||||
pkce: true
|
||||
client_options:
|
||||
identifier: {{ GITLAB_OIDC_CLIENT_ID | to_json }}
|
||||
secret: {{ GITLAB_OIDC_CLIENT_SECRET | to_json }}
|
||||
redirect_uri: {{ GITLAB_OIDC_REDIRECT_URI | to_json }}
|
||||
{% else %}
|
||||
omniauth:
|
||||
enabled: false
|
||||
{% endif %}
|
||||
gitaly:
|
||||
token: {{ GITLAB_GITALY_TOKEN | to_json }}
|
||||
repositories:
|
||||
storages:
|
||||
default:
|
||||
path: /var/opt/gitlab/repo
|
||||
gitaly_address: "tcp://{{ GITLAB_GITALY_SERVICE_NAME }}:{{ GITLAB_GITALY_PORT }}"
|
||||
backup:
|
||||
path: "tmp/backups"
|
||||
gitlab_shell:
|
||||
path: /home/git/gitlab-shell/
|
||||
hooks_path: /home/git/gitlab-shell/hooks/
|
||||
secret_file: /var/opt/gitlab/config/secrets/.gitlab_shell_secret
|
||||
upload_pack: true
|
||||
receive_pack: true
|
||||
ssh_host: {{ GITLAB_HOSTNAME | to_json }}
|
||||
ssh_port: {{ GITLAB_SSH_PORT }}
|
||||
git:
|
||||
bin_path: /usr/bin/git
|
||||
monitoring:
|
||||
ip_whitelist:
|
||||
- 127.0.0.0/8
|
||||
- ::1/128
|
||||
sidekiq_health_checks:
|
||||
enabled: true
|
||||
address: 0.0.0.0
|
||||
port: {{ GITLAB_SIDEKIQ_HEALTH_PORT }}
|
||||
@@ -0,0 +1 @@
|
||||
{{ GITLAB_SHELL_SECRET }}
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
production:
|
||||
url: {{ GITLAB_REDIS_URL | to_json }}
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
production:
|
||||
secret_key_base: {{ lookup('config', application_id, 'credentials.secret_key_base') | to_json }}
|
||||
otp_key_base: {{ lookup('config', application_id, 'credentials.otp_key_base') | to_json }}
|
||||
db_key_base: {{ lookup('config', application_id, 'credentials.db_key_base') | to_json }}
|
||||
encrypted_settings_key_base: {{ lookup('config', application_id, 'credentials.encrypted_settings_key_base') | to_json }}
|
||||
openid_connect_signing_key: |
|
||||
{{ lookup('config', application_id, 'credentials.openid_connect_signing_key') | trim | indent(4, true) }}
|
||||
active_record_encryption_primary_key:
|
||||
- {{ lookup('config', application_id, 'credentials.active_record_encryption_primary_key') | to_json }}
|
||||
active_record_encryption_deterministic_key:
|
||||
- {{ lookup('config', application_id, 'credentials.active_record_encryption_deterministic_key') | to_json }}
|
||||
active_record_encryption_key_derivation_salt: {{ lookup('config', application_id, 'credentials.active_record_encryption_key_derivation_salt') | to_json }}
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
user: git
|
||||
gitlab_url: "http://{{ GITLAB_WORKHORSE_SERVICE_NAME }}:{{ GITLAB_WORKHORSE_PORT }}/"
|
||||
secret_file: /srv/gitlab-secrets/.gitlab_shell_secret
|
||||
auth_file: "/home/git/.ssh/authorized_keys"
|
||||
log_file: "/dev/stdout"
|
||||
log_level: INFO
|
||||
audit_usernames: false
|
||||
sshd:
|
||||
listen: "[::]:{{ GITLAB_SSHD_PORT }}"
|
||||
web_listen: "0.0.0.0:{{ GITLAB_SHELL_METRICS_PORT }}"
|
||||
concurrent_sessions_limit: 100
|
||||
host_key_files:
|
||||
- /srv/ssh-hostkeys/ssh_host_rsa_key
|
||||
- /srv/ssh-hostkeys/ssh_host_ed25519_key
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
:queues:
|
||||
- [post_receive, 5]
|
||||
- [merge, 5]
|
||||
- [update_merge_requests, 3]
|
||||
- [process_commit, 3]
|
||||
- [new_note, 2]
|
||||
- [new_issue, 2]
|
||||
- [new_merge_request, 2]
|
||||
- [build, 2]
|
||||
- [pipeline, 2]
|
||||
- [pipeline_processing, 5]
|
||||
- [pipeline_default, 3]
|
||||
- [pipeline_cache, 3]
|
||||
- [pipeline_hooks, 2]
|
||||
- [gitlab_shell, 2]
|
||||
- [email_receiver, 2]
|
||||
- [emails_on_push, 2]
|
||||
- [mailers, 2]
|
||||
- [invalid_gpg_signature_update, 2]
|
||||
- [create_gpg_signature, 2]
|
||||
- [upload_checksum, 1]
|
||||
- [repository_fork, 1]
|
||||
- [repository_import, 1]
|
||||
- [project_service, 1]
|
||||
- [delete_user, 1]
|
||||
- [delete_merged_branches, 1]
|
||||
- [authorized_projects, 1]
|
||||
- [expire_build_instance_artifacts, 1]
|
||||
- [group_destroy, 1]
|
||||
- [irker, 1]
|
||||
- [namespaceless_project_destroy, 1]
|
||||
- [project_cache, 1]
|
||||
- [project_destroy, 1]
|
||||
- [project_export, 1]
|
||||
- [web_hook, 1]
|
||||
- [repository_check, 1]
|
||||
- [git_garbage_collect, 1]
|
||||
- [reactive_caching, 1]
|
||||
- [cronjob, 1]
|
||||
- [default, 1]
|
||||
- [pages, 1]
|
||||
- [system_hook_push, 1]
|
||||
- [update_user_activity, 1]
|
||||
- [propagate_service_template, 1]
|
||||
- [background_migration, 1]
|
||||
- [gcp_cluster, 1]
|
||||
- [project_migrate_hashed_storage, 1]
|
||||
- [storage_migrator, 1]
|
||||
- [ldap_group_sync, 2]
|
||||
- [geo, 1]
|
||||
- [repository_update_mirror, 1]
|
||||
- [repository_update_remote_mirror, 1]
|
||||
- [project_update_repository_storage, 1]
|
||||
- [admin_emails, 1]
|
||||
- [geo_repository_update, 1]
|
||||
- [elastic_batch_project_indexer, 1]
|
||||
- [elastic_indexer, 1]
|
||||
- [elastic_commit_indexer, 1]
|
||||
- [export_csv, 1]
|
||||
- [object_storage_upload, 1]
|
||||
@@ -0,0 +1,12 @@
|
||||
Rails.application.config.action_mailer.delivery_method = :smtp
|
||||
ActionMailer::Base.delivery_method = :smtp
|
||||
ActionMailer::Base.smtp_settings = {
|
||||
address: {{ GITLAB_EMAIL.host | ruby_dq }},
|
||||
port: {{ GITLAB_EMAIL.port }},
|
||||
user_name: {{ GITLAB_EMAIL.username | ruby_dq }},
|
||||
password: {{ GITLAB_EMAIL.password | ruby_dq }},
|
||||
domain: {{ GITLAB_EMAIL.domain | ruby_dq }},
|
||||
authentication: {{ ':login' if (GITLAB_EMAIL.auth | bool) else 'nil' }},
|
||||
enable_starttls_auto: {{ GITLAB_EMAIL.start_tls | bool | string | lower }},
|
||||
tls: {{ GITLAB_EMAIL.tls | bool | string | lower }}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
shutdown_timeout = "10s"
|
||||
|
||||
[redis]
|
||||
URL = "tcp://{{ GITLAB_REDIS_HOST }}:{{ GITLAB_REDIS_PORT }}"
|
||||
{% if GITLAB_REDIS_PASSWORD %}
|
||||
Password = {{ GITLAB_REDIS_PASSWORD | to_json }}
|
||||
{% endif %}
|
||||
|
||||
[image_resizer]
|
||||
max_scaler_procs = 100
|
||||
max_filesize = 250000
|
||||
|
||||
[[listeners]]
|
||||
network = "tcp"
|
||||
addr = "0.0.0.0:{{ GITLAB_WORKHORSE_PORT }}"
|
||||
@@ -0,0 +1 @@
|
||||
{{ GITLAB_WORKHORSE_SECRET | b64encode }}
|
||||
@@ -1,5 +1,7 @@
|
||||
{% if DEPLOYMENT_MODE == 'swarm' %}
|
||||
GITLAB_OMNIBUS_CONFIG={{ (GITLAB_OMNIBUS_ALL | join('\n')) | ruby_single_line | dotenv_quote }}
|
||||
{% else %}
|
||||
GITLAB_OMNIBUS_CONFIG={{ (GITLAB_OMNIBUS_ALL | join('\n')) | dotenv_quote }}
|
||||
{% endif %}
|
||||
GITLAB_HOST={{ GITLAB_HOSTNAME }}
|
||||
GITLAB_PORT={{ GITLAB_WEB_PORT }}
|
||||
GITLAB_SSH_PORT={{ GITLAB_SSH_PORT }}
|
||||
WAIT_FOR_TIMEOUT=1800
|
||||
SIDEKIQ_TIMEOUT=25
|
||||
ENABLE_BOOTSNAP=true
|
||||
GITLAB_ROOT_PASSWORD={{ GITLAB_INIT_ROOT_PASSWORD | dotenv_quote }}
|
||||
@@ -2,8 +2,21 @@
|
||||
application_id: "web-app-gitlab"
|
||||
entity_name: "{{ application_id | get_entity_name }}"
|
||||
|
||||
swarm_stack_converge_retries: 600
|
||||
|
||||
GITLAB_URL: "{{ lookup('tls', application_id, 'url.base') }}"
|
||||
GITLAB_HOSTNAME: "{{ lookup('domain',application_id) }}"
|
||||
GITLAB_HOSTNAME: "{{ lookup('domain', application_id) }}"
|
||||
GITLAB_TLS_ENABLED: "{{ lookup('tls', application_id, 'enabled') | bool }}"
|
||||
GITLAB_WEB_PORT: "{{ lookup('tls', application_id, 'ports.web') }}"
|
||||
|
||||
GITLAB_VERSION: "{{ lookup('config', application_id, 'services.webservice.version') }}"
|
||||
GITLAB_SSH_PORT: "{{ lookup('config', application_id, 'services.gitlab.ports.public.ssh') }}"
|
||||
GITLAB_WORKHORSE_PORT: "{{ lookup('config', application_id, 'services.gitlab.ports.internal.http') }}"
|
||||
GITLAB_SSHD_PORT: "{{ lookup('config', application_id, 'services.gitlab.ports.internal.ssh') }}"
|
||||
GITLAB_WEBSERVICE_PORT: "{{ lookup('config', application_id, 'services.webservice.ports.internal.http') }}"
|
||||
GITLAB_GITALY_PORT: "{{ lookup('config', application_id, 'services.gitaly.ports.internal.grpc') }}"
|
||||
GITLAB_SIDEKIQ_HEALTH_PORT: "{{ lookup('config', application_id, 'services.sidekiq.ports.internal.http') }}"
|
||||
GITLAB_SHELL_METRICS_PORT: "{{ lookup('config', application_id, 'services.shell.ports.internal.metrics') }}"
|
||||
|
||||
GITLAB_EMAIL: "{{ lookup('email', application_id) }}"
|
||||
GITLAB_EMAIL_ENABLED: "{{ GITLAB_EMAIL.enabled | bool }}"
|
||||
@@ -14,70 +27,9 @@ GITLAB_OIDC_UID_FIELD: "{{ OIDC.ATTRIBUTES.USERNAME }}"
|
||||
GITLAB_OIDC_CLIENT_ID: "{{ OIDC.CLIENT.ID }}"
|
||||
GITLAB_OIDC_CLIENT_SECRET: "{{ OIDC.CLIENT.SECRET }}"
|
||||
GITLAB_OIDC_ISSUER: "{{ OIDC.CLIENT.ISSUER_URL }}"
|
||||
GITLAB_OIDC_REDIRECT_URI: "{{ GITLAB_URL }}/users/auth/openid_connect/callback"
|
||||
GITLAB_OIDC_REDIRECT_URI: "{{ (GITLAB_URL | regex_replace('/$', '')) ~ '/users/auth/openid_connect/callback' }}"
|
||||
|
||||
GITLAB_INIT_ROOT_PASSWORD: "{{ lookup('users', 'administrator').password }}"
|
||||
GITLAB_VERSION: "{{ lookup('config', application_id, 'services.gitlab.version') }}"
|
||||
GITLAB_IMAGE: "{{ lookup('config', application_id, 'services.gitlab.image') }}"
|
||||
GITLAB_CONTAINER: "{{ lookup('config', application_id, 'services.gitlab.name') }}"
|
||||
|
||||
GITLAB_OMNIBUS_BASE:
|
||||
- "external_url '{{ GITLAB_URL }}'"
|
||||
- "postgresql['enable']=false"
|
||||
- "gitlab_rails['gitlab_shell_ssh_port']={{ lookup('config', application_id, 'services.gitlab.ports.public.ssh') }}"
|
||||
- "gitlab_rails['db_adapter']='postgresql'"
|
||||
- "gitlab_rails['db_encoding']='utf8'"
|
||||
- "gitlab_rails['db_host']='{{ lookup('database', application_id, 'host') }}'"
|
||||
- "gitlab_rails['db_port']='{{ lookup('database', application_id, 'port') }}'"
|
||||
- "gitlab_rails['db_username']='{{ lookup('database', application_id, 'username') }}'"
|
||||
- "gitlab_rails['db_password']='{{ lookup('database', application_id, 'password') }}'"
|
||||
- "gitlab_rails['db_database']='{{ lookup('database', application_id, 'name') }}'"
|
||||
- "gitlab_rails['db_pool']={{ POSTGRES_ALLOWED_AVG_CONNECTIONS }}"
|
||||
- "nginx['listen_port']=80"
|
||||
- "nginx['listen_https']=false"
|
||||
- ""
|
||||
- "gitlab_rails['initial_root_password']={{ GITLAB_INIT_ROOT_PASSWORD | ruby_dq }}"
|
||||
- ""
|
||||
- "redis['enable']=false"
|
||||
- "gitlab_rails['redis_host']='{{ lookup('engine', 'redis', application_id, 'host') }}'"
|
||||
- "gitlab_rails['redis_port']='{{ lookup('engine', 'redis', application_id, 'port') }}'"
|
||||
- "gitlab_rails['redis_password']={{ (lookup('engine', 'redis', application_id, 'password') | ruby_dq) if lookup('engine', 'redis', application_id, 'password')
|
||||
else 'nil' }}"
|
||||
|
||||
GITLAB_OMNIBUS_OIDC:
|
||||
- ""
|
||||
- "gitlab_rails['omniauth_enabled']=true"
|
||||
- "gitlab_rails['omniauth_allow_single_sign_on']=['openid_connect']"
|
||||
- "gitlab_rails['omniauth_block_auto_created_users']=false"
|
||||
- "gitlab_rails['omniauth_auto_link_user']=['openid_connect']"
|
||||
- "gitlab_rails['omniauth_providers']=[{ name: 'openid_connect', label: '{{ GITLAB_OIDC_LABEL | replace(\"'\",\"\\\\'\") }}', args: {"
|
||||
- " name: 'openid_connect',"
|
||||
- " scope: ['openid','profile','email'],"
|
||||
- " response_type: 'code',"
|
||||
- " issuer: '{{ GITLAB_OIDC_ISSUER | replace(\"'\",\"\\\\'\") }}',"
|
||||
- " discovery: true,"
|
||||
- " uid_field: '{{ GITLAB_OIDC_UID_FIELD | replace(\"'\",\"\\\\'\") }}',"
|
||||
- " pkce: true,"
|
||||
- " client_options: {"
|
||||
- " identifier: '{{ GITLAB_OIDC_CLIENT_ID | replace(\"'\",\"\\\\'\") }}',"
|
||||
- " secret: '{{ GITLAB_OIDC_CLIENT_SECRET | replace(\"'\",\"\\\\'\") }}',"
|
||||
- " redirect_uri: '{{ GITLAB_OIDC_REDIRECT_URI | replace(\"'\",\"\\\\'\") }}'"
|
||||
- " }"
|
||||
- "} }]"
|
||||
|
||||
GITLAB_OMNIBUS_SMTP:
|
||||
- ""
|
||||
- "gitlab_rails['smtp_enable']=true"
|
||||
- "gitlab_rails['smtp_address']={{ GITLAB_EMAIL.host | ruby_dq }}"
|
||||
- "gitlab_rails['smtp_port']={{ GITLAB_EMAIL.port }}"
|
||||
- "gitlab_rails['smtp_user_name']={{ GITLAB_EMAIL.username | ruby_dq }}"
|
||||
- "gitlab_rails['smtp_password']={{ GITLAB_EMAIL.password | ruby_dq }}"
|
||||
- "gitlab_rails['smtp_domain']={{ GITLAB_EMAIL.domain | ruby_dq }}"
|
||||
- "gitlab_rails['smtp_authentication']={{ (GITLAB_EMAIL.auth | bool) | ternary(\"'login'\", 'nil') }}"
|
||||
- "gitlab_rails['smtp_enable_starttls_auto']={{ GITLAB_EMAIL.start_tls | bool | string | lower }}"
|
||||
- "gitlab_rails['smtp_tls']={{ GITLAB_EMAIL.tls | bool | string | lower }}"
|
||||
- "gitlab_rails['gitlab_email_from']={{ GITLAB_EMAIL['from'] | ruby_dq }}"
|
||||
- "gitlab_rails['gitlab_email_reply_to']={{ GITLAB_EMAIL['from'] | ruby_dq }}"
|
||||
|
||||
GITLAB_OBJSTORE_ENABLED: "{{ lookup('objstore', application_id, 'enabled') | bool }}"
|
||||
GITLAB_OBJSTORE_BUCKET: "{{ lookup('objstore', application_id, 'bucket') }}"
|
||||
@@ -86,27 +38,33 @@ GITLAB_OBJSTORE_SECRET_KEY: "{{ lookup('objstore', application_id, 'secret_key')
|
||||
GITLAB_OBJSTORE_ENDPOINT: "{{ lookup('objstore', application_id, 'url') }}"
|
||||
GITLAB_OBJSTORE_REGION: "{{ lookup('objstore', application_id, 'region') }}"
|
||||
|
||||
GITLAB_OMNIBUS_OBJSTORE:
|
||||
- ""
|
||||
- "gitlab_rails['object_store']['enabled']=true"
|
||||
- "gitlab_rails['object_store']['proxy_download']=true"
|
||||
- "gitlab_rails['object_store']['connection']={"
|
||||
- " 'provider' => 'AWS',"
|
||||
- " 'endpoint' => {{ GITLAB_OBJSTORE_ENDPOINT | ruby_dq }},"
|
||||
- " 'region' => {{ GITLAB_OBJSTORE_REGION | ruby_dq }},"
|
||||
- " 'path_style' => true,"
|
||||
- " 'aws_access_key_id' => {{ GITLAB_OBJSTORE_ACCESS_KEY | ruby_dq }},"
|
||||
- " 'aws_secret_access_key' => {{ GITLAB_OBJSTORE_SECRET_KEY | ruby_dq }}"
|
||||
- "}"
|
||||
- "gitlab_rails['object_store']['objects']['artifacts']['bucket']={{ (GITLAB_OBJSTORE_BUCKET ~ '/artifacts') | ruby_dq }}"
|
||||
- "gitlab_rails['object_store']['objects']['external_diffs']['bucket']={{ (GITLAB_OBJSTORE_BUCKET ~ '/external-diffs') | ruby_dq }}"
|
||||
- "gitlab_rails['object_store']['objects']['lfs']['bucket']={{ (GITLAB_OBJSTORE_BUCKET ~ '/lfs') | ruby_dq }}"
|
||||
- "gitlab_rails['object_store']['objects']['uploads']['bucket']={{ (GITLAB_OBJSTORE_BUCKET ~ '/uploads') | ruby_dq }}"
|
||||
- "gitlab_rails['object_store']['objects']['packages']['bucket']={{ (GITLAB_OBJSTORE_BUCKET ~ '/packages') | ruby_dq }}"
|
||||
- "gitlab_rails['object_store']['objects']['dependency_proxy']['bucket']={{ (GITLAB_OBJSTORE_BUCKET ~ '/dependency-proxy') | ruby_dq }}"
|
||||
- "gitlab_rails['object_store']['objects']['terraform_state']['bucket']={{ (GITLAB_OBJSTORE_BUCKET ~ '/terraform-state') | ruby_dq }}"
|
||||
- "gitlab_rails['object_store']['objects']['ci_secure_files']['bucket']={{ (GITLAB_OBJSTORE_BUCKET ~ '/ci-secure-files') | ruby_dq }}"
|
||||
- "gitlab_rails['object_store']['objects']['pages']['bucket']={{ (GITLAB_OBJSTORE_BUCKET ~ '/pages') | ruby_dq }}"
|
||||
GITLAB_REDIS_HOST: "{{ lookup('engine', 'redis', application_id, 'host') }}"
|
||||
GITLAB_REDIS_PORT: "{{ lookup('engine', 'redis', application_id, 'port') }}"
|
||||
GITLAB_REDIS_PASSWORD: "{{ lookup('engine', 'redis', application_id, 'password') }}"
|
||||
GITLAB_REDIS_URL: "redis://{% if GITLAB_REDIS_PASSWORD %}:{{ GITLAB_REDIS_PASSWORD | urlencode }}@{% endif %}{{ GITLAB_REDIS_HOST }}:{{ GITLAB_REDIS_PORT }}"
|
||||
|
||||
GITLAB_OMNIBUS_ALL: "{{ GITLAB_OMNIBUS_BASE + (GITLAB_OMNIBUS_OIDC if GITLAB_OIDC_ENABLED else []) + (GITLAB_OMNIBUS_SMTP if GITLAB_EMAIL_ENABLED else []) + (GITLAB_OMNIBUS_OBJSTORE
|
||||
if GITLAB_OBJSTORE_ENABLED else []) }}"
|
||||
GITLAB_SHELL_SECRET: "{{ lookup('config', application_id, 'credentials.gitlab_shell_secret') }}"
|
||||
GITLAB_GITALY_TOKEN: "{{ lookup('config', application_id, 'credentials.gitaly_token') }}"
|
||||
GITLAB_WORKHORSE_SECRET: "{{ lookup('config', application_id, 'credentials.workhorse_secret') }}"
|
||||
|
||||
GITLAB_CONFIG_DIR: "{{ lookup('container', application_id, 'directories.config') }}"
|
||||
GITLAB_CONFIG_RAILS_DIR: "{{ [GITLAB_CONFIG_DIR, 'rails'] | path_join }}"
|
||||
GITLAB_CONFIG_SECRETS_DIR: "{{ [GITLAB_CONFIG_DIR, 'secrets'] | path_join }}"
|
||||
GITLAB_CONFIG_WORKHORSE_DIR: "{{ [GITLAB_CONFIG_DIR, 'workhorse'] | path_join }}"
|
||||
GITLAB_CONFIG_SHELL_DIR: "{{ [GITLAB_CONFIG_DIR, 'shell'] | path_join }}"
|
||||
GITLAB_CONFIG_GITALY_DIR: "{{ [GITLAB_CONFIG_DIR, 'gitaly'] | path_join }}"
|
||||
GITLAB_CONFIG_HOSTKEYS_DIR: "{{ [GITLAB_CONFIG_DIR, 'hostkeys'] | path_join }}"
|
||||
|
||||
GITLAB_WEBSERVICE_SERVICE_NAME: "webservice"
|
||||
GITLAB_SIDEKIQ_SERVICE_NAME: "sidekiq"
|
||||
GITLAB_WORKHORSE_SERVICE_NAME: "workhorse"
|
||||
GITLAB_GITALY_SERVICE_NAME: "gitaly"
|
||||
GITLAB_SHELL_SERVICE_NAME: "shell"
|
||||
GITLAB_MIGRATIONS_SERVICE_NAME: "migrations"
|
||||
|
||||
GITLAB_WEBSERVICE_CONTAINER: "{{ lookup('config', application_id, 'services.webservice.name') }}"
|
||||
GITLAB_SIDEKIQ_CONTAINER: "{{ lookup('config', application_id, 'services.sidekiq.name') }}"
|
||||
GITLAB_WORKHORSE_CONTAINER: "{{ lookup('config', application_id, 'services.workhorse.name') }}"
|
||||
GITLAB_GITALY_CONTAINER: "{{ lookup('config', application_id, 'services.gitaly.name') }}"
|
||||
GITLAB_SHELL_CONTAINER: "{{ lookup('config', application_id, 'services.shell.name') }}"
|
||||
GITLAB_MIGRATIONS_CONTAINER: "{{ lookup('config', application_id, 'services.migrations.name') }}"
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
- name: 🔍 Resolve GitLab integration provisioning facts
|
||||
set_fact:
|
||||
nextcloud_gl_container: "{{ lookup('config', 'web-app-gitlab', 'services.gitlab.name') }}"
|
||||
nextcloud_gl_container: "{{ lookup('config', 'web-app-gitlab', 'services.webservice.name') }}"
|
||||
nextcloud_gl_nc_base_url: "{{ lookup('tls', 'web-app-nextcloud', 'url.base') | regex_replace('/$', '') }}"
|
||||
changed_when: false
|
||||
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
- -e
|
||||
- "NC_HAS_SECRET={{ nextcloud_gl_client_secret_current.stdout | trim }}"
|
||||
- "{{ nextcloud_gl_container }}" # nocheck: container-exec-resolver runs only behind the bare-name partner probe gate, compose co-location
|
||||
- gitlab-rails
|
||||
- /srv/gitlab/bin/rails
|
||||
- runner
|
||||
- -e
|
||||
- production
|
||||
- "-"
|
||||
stdin: "{{ lookup('file', 'gitlab/provision_oauth_app.rb') }}"
|
||||
register: nextcloud_gl_provision
|
||||
|
||||
@@ -266,6 +266,96 @@ class TestComposeVolumes(unittest.TestCase):
|
||||
self.assertEqual(vol["driver_opts"]["device"], f"{_DIR_VAR_LIB}/app_images")
|
||||
self.assertNotIn("nfs", vol)
|
||||
|
||||
def test_swarm_nfs_false_opt_out_stays_plain_local_volume(self):
|
||||
apps = self._base_apps()
|
||||
rendered = _call(
|
||||
apps,
|
||||
"app",
|
||||
extra_volumes={
|
||||
"repositories": {"name": "app_repositories", "nfs": False},
|
||||
"shared": {"name": "app_shared"},
|
||||
},
|
||||
deployment_mode="swarm",
|
||||
storage={
|
||||
"backend": "nfs",
|
||||
"nfs": {"server": "10.0.0.20", "export_base": "/srv/nfs"},
|
||||
},
|
||||
)
|
||||
data = self._parse_yaml(rendered)
|
||||
|
||||
repositories = data["volumes"]["repositories"]
|
||||
self.assertEqual(repositories, {"name": "app_repositories"})
|
||||
shared = data["volumes"]["shared"]
|
||||
self.assertEqual(shared["driver"], "local")
|
||||
self.assertEqual(shared["driver_opts"]["device"], f"{_DIR_VAR_LIB}/app_shared")
|
||||
|
||||
def test_swarm_nfs_false_opt_out_from_meta_volumes(self):
|
||||
apps = self._base_apps()
|
||||
apps["app"]["volumes"] = {
|
||||
"repositories": {
|
||||
"type": "volume",
|
||||
"name": "app_repositories",
|
||||
"nfs": False,
|
||||
"mounts": [{"service": "gitaly", "target": "/home/git/repositories"}],
|
||||
},
|
||||
"shared": {
|
||||
"type": "volume",
|
||||
"name": "app_shared",
|
||||
"mounts": [{"service": "web", "target": "/srv/app/shared"}],
|
||||
},
|
||||
}
|
||||
rendered = _call(
|
||||
apps,
|
||||
"app",
|
||||
deployment_mode="swarm",
|
||||
storage={
|
||||
"backend": "nfs",
|
||||
"nfs": {"server": "10.0.0.20", "export_base": "/srv/nfs"},
|
||||
},
|
||||
)
|
||||
data = self._parse_yaml(rendered)
|
||||
|
||||
self.assertEqual(data["volumes"]["repositories"], {"name": "app_repositories"})
|
||||
self.assertIn("driver_opts", data["volumes"]["shared"])
|
||||
|
||||
def test_swarm_nfs_dict_value_keeps_rewrite(self):
|
||||
apps = self._base_apps()
|
||||
apps["app"]["volumes"] = {
|
||||
"data": {
|
||||
"type": "volume",
|
||||
"name": "app_data",
|
||||
"nfs": {"uid": 1000, "gid": 1000, "mode": "0750"},
|
||||
"mounts": [{"service": "web", "target": "/data"}],
|
||||
},
|
||||
}
|
||||
rendered = _call(
|
||||
apps,
|
||||
"app",
|
||||
deployment_mode="swarm",
|
||||
storage={
|
||||
"backend": "nfs",
|
||||
"nfs": {"server": "10.0.0.20", "export_base": "/srv/nfs"},
|
||||
},
|
||||
)
|
||||
data = self._parse_yaml(rendered)
|
||||
|
||||
vol = data["volumes"]["data"]
|
||||
self.assertEqual(vol["driver_opts"]["device"], f"{_DIR_VAR_LIB}/app_data")
|
||||
self.assertNotIn("nfs", vol)
|
||||
|
||||
def test_compose_mode_nfs_false_leaves_no_nfs_key(self):
|
||||
apps = self._base_apps()
|
||||
rendered = _call(
|
||||
apps,
|
||||
"app",
|
||||
extra_volumes={"repositories": {"name": "app_repositories", "nfs": False}},
|
||||
deployment_mode="compose",
|
||||
storage={"backend": "local"},
|
||||
)
|
||||
data = self._parse_yaml(rendered)
|
||||
|
||||
self.assertEqual(data["volumes"]["repositories"], {"name": "app_repositories"})
|
||||
|
||||
def test_swarm_pinned_role_stays_node_local(self):
|
||||
apps = self._base_apps()
|
||||
with mock.patch(
|
||||
|
||||
@@ -3,55 +3,45 @@ import unittest
|
||||
from plugins.filter.ruby import FilterModule
|
||||
|
||||
|
||||
class TestRubySingleLine(unittest.TestCase):
|
||||
"""ruby_single_line collapses a multi-line Ruby program to one physical
|
||||
line so a swarm `docker stack deploy` env-file (which reads embedded
|
||||
newlines as separate, whitespace-named entries) keeps a value like
|
||||
GITLAB_OMNIBUS_CONFIG intact: statement-boundary newlines (bracket depth 0)
|
||||
become ';', newlines inside an open bracket or a string become a space.
|
||||
"""
|
||||
class TestRubyDq(unittest.TestCase):
|
||||
"""ruby_dq escapes a value for a Ruby double-quoted string and wraps it
|
||||
in double quotes; used by the gitlab smtp_settings.rb initializer."""
|
||||
|
||||
def setUp(self):
|
||||
self.f = FilterModule().filters()["ruby_single_line"]
|
||||
self.f = FilterModule().filters()["ruby_dq"]
|
||||
|
||||
def test_none_returns_empty(self):
|
||||
self.assertEqual(self.f(None), "")
|
||||
def test_none_returns_empty_quotes(self):
|
||||
self.assertEqual(self.f(None), '""')
|
||||
|
||||
def test_single_line_unchanged(self):
|
||||
self.assertEqual(self.f("a = 1"), "a = 1")
|
||||
def test_empty_string(self):
|
||||
self.assertEqual(self.f(""), '""')
|
||||
|
||||
def test_depth_zero_newline_becomes_semicolon(self):
|
||||
self.assertEqual(self.f("a = 1\nb = 2"), "a = 1;b = 2")
|
||||
def test_plain_value_wrapped(self):
|
||||
self.assertEqual(self.f("secret"), '"secret"')
|
||||
|
||||
def test_newline_inside_parens_becomes_space(self):
|
||||
self.assertEqual(self.f("foo(\na\n)"), "foo( a )")
|
||||
def test_backslash_escaped(self):
|
||||
self.assertEqual(self.f("a\\b"), '"a\\\\b"')
|
||||
|
||||
def test_newline_inside_brackets_becomes_space(self):
|
||||
self.assertEqual(self.f("x = [\n1\n]"), "x = [ 1 ]")
|
||||
def test_double_quote_escaped(self):
|
||||
self.assertEqual(self.f('a"b'), '"a\\"b"')
|
||||
|
||||
def test_newline_inside_braces_becomes_space(self):
|
||||
self.assertEqual(self.f("h = {\n}"), "h = { }")
|
||||
def test_newline_escaped(self):
|
||||
self.assertEqual(self.f("a\nb"), '"a\\nb"')
|
||||
|
||||
def test_newline_inside_string_becomes_space(self):
|
||||
self.assertEqual(self.f("x = 'a\nb'"), "x = 'a b'")
|
||||
def test_carriage_return_escaped(self):
|
||||
self.assertEqual(self.f("a\rb"), '"a\\rb"')
|
||||
|
||||
def test_escaped_quote_in_string_does_not_break_depth(self):
|
||||
# backslash + next char pass through verbatim, so the escaped quote
|
||||
# does not terminate the string and the following newline is still a
|
||||
# depth-0 statement boundary.
|
||||
self.assertEqual(self.f('a = "x\\"y"\nb = 2'), 'a = "x\\"y";b = 2')
|
||||
def test_tab_escaped(self):
|
||||
self.assertEqual(self.f("a\tb"), '"a\\tb"')
|
||||
|
||||
def test_realistic_gitlab_omnibus_config(self):
|
||||
src = (
|
||||
"external_url 'https://gitlab.example'\ngitlab_rails['smtp_enable'] = true"
|
||||
)
|
||||
self.assertEqual(
|
||||
self.f(src),
|
||||
"external_url 'https://gitlab.example';gitlab_rails['smtp_enable'] = true",
|
||||
)
|
||||
def test_backslash_before_quote_order(self):
|
||||
self.assertEqual(self.f('\\"'), '"\\\\\\""')
|
||||
|
||||
def test_dollar_and_single_quote_untouched(self):
|
||||
self.assertEqual(self.f("$a'b"), '"$a\'b"')
|
||||
|
||||
def test_non_string_is_stringified(self):
|
||||
self.assertEqual(self.f(123), "123")
|
||||
self.assertEqual(self.f(123), '"123"')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -2,8 +2,12 @@ import base64
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||
from cryptography.hazmat.primitives.asymmetric import ec, rsa
|
||||
from cryptography.hazmat.primitives.serialization import (
|
||||
Encoding,
|
||||
PublicFormat,
|
||||
load_pem_private_key,
|
||||
)
|
||||
|
||||
from utils.manager.value_generator import ValueGenerator
|
||||
|
||||
@@ -74,6 +78,20 @@ class TestValueGenerator(unittest.TestCase):
|
||||
self.assertEqual(len(point), 65)
|
||||
self.assertEqual(point[0], 0x04)
|
||||
|
||||
def test_generate_value_rsa_pem_2048(self):
|
||||
v = self.vg.generate_value("rsa_pem_2048")
|
||||
self.assertTrue(v.startswith("-----BEGIN RSA PRIVATE KEY-----"))
|
||||
self.assertTrue(v.rstrip().endswith("-----END RSA PRIVATE KEY-----"))
|
||||
key = load_pem_private_key(v.encode(), password=None)
|
||||
self.assertIsInstance(key, rsa.RSAPrivateKey)
|
||||
self.assertEqual(key.key_size, 2048)
|
||||
|
||||
def test_generate_value_rsa_pem_2048_is_not_cached(self):
|
||||
self.assertNotEqual(
|
||||
self.vg.generate_value("rsa_pem_2048"),
|
||||
self.vg.generate_value("rsa_pem_2048"),
|
||||
)
|
||||
|
||||
def test_vapid_keypair_is_cached(self):
|
||||
self.assertEqual(
|
||||
self.vg.generate_vapid_keypair(), self.vg.generate_vapid_keypair()
|
||||
|
||||
@@ -5,8 +5,13 @@ import secrets
|
||||
import string
|
||||
|
||||
import bcrypt
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||
from cryptography.hazmat.primitives.asymmetric import ec, rsa
|
||||
from cryptography.hazmat.primitives.serialization import (
|
||||
Encoding,
|
||||
NoEncryption,
|
||||
PrivateFormat,
|
||||
PublicFormat,
|
||||
)
|
||||
|
||||
|
||||
class ValueGenerator:
|
||||
@@ -74,6 +79,7 @@ class ValueGenerator:
|
||||
• "base64_prefixed_32"
|
||||
• "vapid_private"
|
||||
• "vapid_public"
|
||||
• "rsa_pem_2048"
|
||||
"""
|
||||
if algorithm == "random_hex":
|
||||
return secrets.token_hex(64)
|
||||
@@ -105,4 +111,9 @@ class ValueGenerator:
|
||||
return self.generate_vapid_keypair()[0]
|
||||
if algorithm == "vapid_public":
|
||||
return self.generate_vapid_keypair()[1]
|
||||
if algorithm == "rsa_pem_2048":
|
||||
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
return key.private_bytes(
|
||||
Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()
|
||||
).decode()
|
||||
return "undefined"
|
||||
Reference in new issue
Block a user