From 5741bbf9b6336233eef07e824718e3b8e15e89de Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Wed, 10 Sep 2025 11:47:57 -0400 Subject: [PATCH 01/24] Refactored compose environment for running local with full cluster including pulp3 primary and secondary (`make run-cluster`) Signed-off-by: Geoff Wilson --- Dockerfile | 4 +- Makefile | 50 ++-- assets/certs/.gitkeep | 0 assets/keys/.gitkeep | 0 assets/nginx-conf/nginx-primary.conf | 137 +++++++++++ assets/nginx-conf/nginx-secondary.conf | 137 +++++++++++ assets/nginx-conf/nginx.conf.template | 89 ++++++++ assets/settings_primary.py | 19 ++ assets/settings_secondary.py | 19 ++ .../docker-compose.yml | 0 docker/local/config.ini | 43 ++++ docker/local/docker-compose.yml | 71 ++++++ docker/local/pulp-config.yml | 35 +++ docker/local/pulp-primary.yml | 215 ++++++++++++++++++ docker/local/pulp-secondary.yml | 215 ++++++++++++++++++ dockercompose-pulp3.yml => docker/pulp3.yml | 0 dockercompose-tests.yml | 32 --- requirements.txt | 4 +- 18 files changed, 1021 insertions(+), 49 deletions(-) create mode 100644 assets/certs/.gitkeep create mode 100644 assets/keys/.gitkeep create mode 100644 assets/nginx-conf/nginx-primary.conf create mode 100644 assets/nginx-conf/nginx-secondary.conf create mode 100644 assets/nginx-conf/nginx.conf.template create mode 100644 assets/settings_primary.py create mode 100644 assets/settings_secondary.py rename dockercompose-local.yml => docker/docker-compose.yml (100%) create mode 100644 docker/local/config.ini create mode 100644 docker/local/docker-compose.yml create mode 100644 docker/local/pulp-config.yml create mode 100644 docker/local/pulp-primary.yml create mode 100644 docker/local/pulp-secondary.yml rename dockercompose-pulp3.yml => docker/pulp3.yml (100%) delete mode 100644 dockercompose-tests.yml diff --git a/Dockerfile b/Dockerfile index 4e5129f..d31dd7e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ RUN mkdir -p /pulp_manager \ && groupadd pulp_manager \ && useradd -u 10001 pulp_manager -g pulp_manager -d /pulp_manager/ \ && apt-get update \ - && apt-get install -y python3-venv netcat \ + && apt-get install -y python3-venv netcat-openbsd \ && python3 -m venv /opt/venv WORKDIR /pulp_manager @@ -33,7 +33,7 @@ RUN /opt/venv/bin/pip install --upgrade pip \ FROM base as final # Install runtime dependencies including Git and make -RUN apt-get update && apt-get install -y netcat git make python3-dev libsasl2-dev libldap2-dev libssl-dev default-libmysqlclient-dev build-essential && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y netcat-openbsd git make python3-dev libsasl2-dev libldap2-dev libssl-dev default-libmysqlclient-dev build-essential && rm -rf /var/lib/apt/lists/* # Copy virtual environment from builder stage COPY --from=builder /opt/venv /opt/venv diff --git a/Makefile b/Makefile index 8948abe..bd30fe0 100644 --- a/Makefile +++ b/Makefile @@ -1,25 +1,23 @@ -.DEFAULT_GOAL:=all +.DEFAULT_GOAL:=h ROOT_DIR := $(dir $(lastword $(MAKEFILE_LIST))) PKG_NAME := pulp_manager -.PHONY : all -all: +.PHONY : h help +h help: @printf "%s\n" "Usage: make " @printf "\n%s\n" "Targets:" @printf " %-22s%s\n" \ + "h|help" "Print this help" \ "t|test" "Run all tests" \ "l|lint" "Run lint" \ "c|cover" "Run coverage for all tests" \ "venv" "Create virtualenv" \ "bdist" "Create wheel file" \ "clean" "Clean workspace" \ - "h|help" "Print this help" - "run-pulp3" "Start Pulp 3 locally with Docker Compose" - - -.PHONY : h help -h help: all + "run-pulp3" "Start Pulp 3 locally with Docker Compose" \ + "run-pulp-manager" "Start Pulp Manager with Docker Compose" \ + "run-cluster" "Start Pulp3 + Pulp Manger cluster with Docker Compose" .PHONY : l lint l lint: venv @@ -45,15 +43,41 @@ venv: requirements.txt run-pulp-manager: setup-network @echo "Starting local Docker Compose environment..." - docker compose -f dockercompose-local.yml up --build + docker compose -f docker/docker-compose.yml up --build .PHONY : run-pulp3 -run-pulp3: setup-network +run-pulp3: setup-network setup-pulp-keys @echo "Starting Pulp 3 locally with Docker Compose..." - docker compose -f ./dockercompose-pulp3.yml up --build + docker compose -f docker/pulp3.yml up --build + +.PHONY : run-cluster +run-cluster: setup-network setup-pulp-keys + @echo "Starting complete local cluster with Pulp Manager, Primary and Secondary Pulp instances..." + docker compose -f docker/local/docker-compose.yml \ + -f docker/local/pulp-primary.yml \ + -f docker/local/pulp-secondary.yml up --build setup-network: @echo "Creating or verifying network..." docker network inspect pulp-net >/dev/null 2>&1 || \ docker network create pulp-net - @echo "Network setup completed." \ No newline at end of file + @echo "Network setup completed." + +setup-pulp-keys: + @echo "Checking for Pulp encryption keys..." + @mkdir -p assets/certs assets/keys assets/nginx-conf + @if [ ! -f assets/certs/database_fields.symmetric.key ]; then \ + echo "Generating database encryption key..."; \ + openssl rand -base64 32 > assets/certs/database_fields.symmetric.key; \ + echo "Database encryption key created."; \ + else \ + echo "Database encryption key already exists."; \ + fi + @if [ ! -f assets/keys/container_auth_private_key.pem ]; then \ + echo "Generating container auth keys..."; \ + openssl ecparam -genkey -name secp256r1 -noout -out assets/keys/container_auth_private_key.pem; \ + openssl ec -in assets/keys/container_auth_private_key.pem -pubout -out assets/keys/container_auth_public_key.pem; \ + echo "Container auth keys created."; \ + else \ + echo "Container auth keys already exist."; \ + fi diff --git a/assets/certs/.gitkeep b/assets/certs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/keys/.gitkeep b/assets/keys/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/nginx-conf/nginx-primary.conf b/assets/nginx-conf/nginx-primary.conf new file mode 100644 index 0000000..de9f7e4 --- /dev/null +++ b/assets/nginx-conf/nginx-primary.conf @@ -0,0 +1,137 @@ +error_log /dev/stdout info; +worker_processes 1; +events { + worker_connections 1024; # increase if you have lots of clients + accept_mutex off; # set to 'on' if nginx worker_processes > 1 +} + +http { + access_log /dev/stdout; + include mime.types; + # fallback in case we can't determine a type + default_type application/octet-stream; + sendfile on; + + # If left at the default of 1024, nginx emits a warning about being unable + # to build optimal hash types. + types_hash_max_size 4096; + + server { + # This logic enables us to have multiple servers, and check to see + # if they are scaled every 10 seconds. + # https://www.nginx.com/blog/dns-service-discovery-nginx-plus#domain-name-variable + # https://serverfault.com/a/821625/189494 + resolver 127.0.0.11 valid=10s; + set $pulp_api pulp-api-primary; + set $pulp_content pulp-content-primary; + + # Gunicorn docs suggest the use of the "deferred" directive on Linux. + listen 8080 default_server deferred; + listen [::]:8080 default_server deferred; + + # If you have a domain name, this is where to add it + server_name $hostname; + + # The default client_max_body_size is 1m. Clients uploading + # files larger than this will need to chunk said files. + client_max_body_size 10m; + + # Gunicorn docs suggest this value. + keepalive_timeout 5; + + # static files that can change dynamically, or are needed for TLS + # purposes are served through the webserver. + root /opt/app-root/src; + + location /pulp/content/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_content:24816; + } + + location /pulp/api/v3/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + location /auth/login/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + # Additional Pulp service endpoints + location /pypi/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + location /v2/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + client_max_body_size 0; + } + + location /extensions/v2/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + location /pulp/container/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://$pulp_content:24816; + } + + location /token/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + location /pulp_ansible/galaxy/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + location / { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + # static files are served through whitenoise - http://whitenoise.evans.io/en/stable/ + } + } +} \ No newline at end of file diff --git a/assets/nginx-conf/nginx-secondary.conf b/assets/nginx-conf/nginx-secondary.conf new file mode 100644 index 0000000..c94d26a --- /dev/null +++ b/assets/nginx-conf/nginx-secondary.conf @@ -0,0 +1,137 @@ +error_log /dev/stdout info; +worker_processes 1; +events { + worker_connections 1024; # increase if you have lots of clients + accept_mutex off; # set to 'on' if nginx worker_processes > 1 +} + +http { + access_log /dev/stdout; + include mime.types; + # fallback in case we can't determine a type + default_type application/octet-stream; + sendfile on; + + # If left at the default of 1024, nginx emits a warning about being unable + # to build optimal hash types. + types_hash_max_size 4096; + + server { + # This logic enables us to have multiple servers, and check to see + # if they are scaled every 10 seconds. + # https://www.nginx.com/blog/dns-service-discovery-nginx-plus#domain-name-variable + # https://serverfault.com/a/821625/189494 + resolver 127.0.0.11 valid=10s; + set $pulp_api pulp-api-secondary; + set $pulp_content pulp-content-secondary; + + # Gunicorn docs suggest the use of the "deferred" directive on Linux. + listen 8080 default_server deferred; + listen [::]:8080 default_server deferred; + + # If you have a domain name, this is where to add it + server_name $hostname; + + # The default client_max_body_size is 1m. Clients uploading + # files larger than this will need to chunk said files. + client_max_body_size 10m; + + # Gunicorn docs suggest this value. + keepalive_timeout 5; + + # static files that can change dynamically, or are needed for TLS + # purposes are served through the webserver. + root /opt/app-root/src; + + location /pulp/content/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_content:24816; + } + + location /pulp/api/v3/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + location /auth/login/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + # Additional Pulp service endpoints + location /pypi/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + location /v2/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + client_max_body_size 0; + } + + location /extensions/v2/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + location /pulp/container/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://$pulp_content:24816; + } + + location /token/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + location /pulp_ansible/galaxy/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + location / { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + # static files are served through whitenoise - http://whitenoise.evans.io/en/stable/ + } + } +} \ No newline at end of file diff --git a/assets/nginx-conf/nginx.conf.template b/assets/nginx-conf/nginx.conf.template new file mode 100644 index 0000000..3707d09 --- /dev/null +++ b/assets/nginx-conf/nginx.conf.template @@ -0,0 +1,89 @@ +error_log /dev/stdout info; +worker_processes 1; +events { + worker_connections 1024; # increase if you have lots of clients + accept_mutex off; # set to 'on' if nginx worker_processes > 1 +} + +http { + access_log /dev/stdout; + include mime.types; + # fallback in case we can't determine a type + default_type application/octet-stream; + sendfile on; + + # If left at the default of 1024, nginx emits a warning about being unable + # to build optimal hash types. + types_hash_max_size 4096; + + server { + # This logic enables us to have multiple servers, and check to see + # if they are scaled every 10 seconds. + # https://www.nginx.com/blog/dns-service-discovery-nginx-plus#domain-name-variable + # https://serverfault.com/a/821625/189494 + resolver $NAMESERVER valid=10s; + set $pulp_api pulp_api; + set $pulp_content pulp_content; + + # Gunicorn docs suggest the use of the "deferred" directive on Linux. + listen 8080 default_server deferred; + listen [::]:8080 default_server deferred; + + # If you have a domain name, this is where to add it + server_name $hostname; + + # The default client_max_body_size is 1m. Clients uploading + # files larger than this will need to chunk said files. + client_max_body_size 10m; + + # Gunicorn docs suggest this value. + keepalive_timeout 5; + + # static files that can change dynamically, or are needed for TLS + # purposes are served through the webserver. + root /opt/app-root/src; + + location /pulp/content/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_content:24816; + } + + location /pulp/api/v3/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + location /auth/login/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + include /opt/app-root/etc/nginx.default.d/*.conf; + + location / { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + # static files are served through whitenoise - http://whitenoise.evans.io/en/stable/ + } + } +} \ No newline at end of file diff --git a/assets/settings_primary.py b/assets/settings_primary.py new file mode 100644 index 0000000..a88eef4 --- /dev/null +++ b/assets/settings_primary.py @@ -0,0 +1,19 @@ +SECRET_KEY = "aabbcc" +CONTENT_ORIGIN = "http://pulp-content-primary:24816" +DATABASES = {"default": {"HOST": "postgres-primary", "ENGINE": "django.db.backends.postgresql", "NAME": "pulp_primary", "USER": "pulp", "PASSWORD": "password", "PORT": "5432", "CONN_MAX_AGE": 0, "OPTIONS": {"sslmode": "prefer"}}} +CACHE_ENABLED = True +REDIS_HOST = "redis-primary" +REDIS_PORT = 6379 +REDIS_PASSWORD = "" +ANSIBLE_API_HOSTNAME = "http://pulp-api-primary:24817" +ANSIBLE_CONTENT_HOSTNAME = "http://pulp-content-primary:24816/pulp/content" +ALLOWED_IMPORT_PATHS = ["/tmp"] +ALLOWED_EXPORT_PATHS = ["/tmp"] +TOKEN_SERVER = "http://pulp-api-primary:24817/token/" +TOKEN_AUTH_DISABLED = False +TOKEN_SIGNATURE_ALGORITHM = "ES256" +PUBLIC_KEY_PATH = "/etc/pulp/keys/container_auth_public_key.pem" +PRIVATE_KEY_PATH = "/etc/pulp/keys/container_auth_private_key.pem" +ANALYTICS = False +STATIC_ROOT = "/var/lib/operator/static/" +ALLOWED_HOSTS = ['pulp-web-primary:8080', 'localhost', '127.0.0.1', '[::1]'] \ No newline at end of file diff --git a/assets/settings_secondary.py b/assets/settings_secondary.py new file mode 100644 index 0000000..908b9c7 --- /dev/null +++ b/assets/settings_secondary.py @@ -0,0 +1,19 @@ +SECRET_KEY = "aabbcc" +CONTENT_ORIGIN = "http://pulp-content-secondary:24816" +DATABASES = {"default": {"HOST": "postgres-secondary", "ENGINE": "django.db.backends.postgresql", "NAME": "pulp_secondary", "USER": "pulp", "PASSWORD": "password", "PORT": "5432", "CONN_MAX_AGE": 0, "OPTIONS": {"sslmode": "prefer"}}} +CACHE_ENABLED = True +REDIS_HOST = "redis-secondary" +REDIS_PORT = 6379 +REDIS_PASSWORD = "" +ANSIBLE_API_HOSTNAME = "http://pulp-api-secondary:24817" +ANSIBLE_CONTENT_HOSTNAME = "http://pulp-content-secondary:24816/pulp/content" +ALLOWED_IMPORT_PATHS = ["/tmp"] +ALLOWED_EXPORT_PATHS = ["/tmp"] +TOKEN_SERVER = "http://pulp-api-secondary:24817/token/" +TOKEN_AUTH_DISABLED = False +TOKEN_SIGNATURE_ALGORITHM = "ES256" +PUBLIC_KEY_PATH = "/etc/pulp/keys/container_auth_public_key.pem" +PRIVATE_KEY_PATH = "/etc/pulp/keys/container_auth_private_key.pem" +ANALYTICS = False +STATIC_ROOT = "/var/lib/operator/static/" +ALLOWED_HOSTS = ['pulp-web-secondary:8080', 'localhost', '127.0.0.1', '[::1]'] \ No newline at end of file diff --git a/dockercompose-local.yml b/docker/docker-compose.yml similarity index 100% rename from dockercompose-local.yml rename to docker/docker-compose.yml diff --git a/docker/local/config.ini b/docker/local/config.ini new file mode 100644 index 0000000..1a35a5d --- /dev/null +++ b/docker/local/config.ini @@ -0,0 +1,43 @@ +;[ca] +;root_ca_file_path="" + +[auth] +method=ldap +use_ssl=true +ldap_servers=dc.example.com +base_dn=DC=example,DC=com +default_domain=example.com +jwt_algorithm=HS256 +jwt_token_lifetime_mins=480 +admin_group=pulpmaster-rw + +[pulp] +deb_signing_service=pulp_deb +banned_package_regex=bannedexample|another +internal_domains=pulp-web-primary,pulp-web-secondary +git_repo_config=https://git.example.com/Pulp-Repo-Config +git_repo_config_dir=repo_config +password=password +internal_package_prefix=int_ +package_name_replacement_pattern= +package_name_replacement_rule= +;may need to be false if using http proxy to remote +remote_tls_validation=false + +[redis] +host=redis-manager +port=6379 +db=0 +max_page_size=24 + +[remotes] +sock_connect_timeout=120.0 +sock_read_timeout=600.0 + +[paging] +default_page_size=50 +max_page_size=20000 + +[vault] +vault_addr=http://127.0.0.1:8200 +repo_secret_namespace=cle-secrets-common-dev \ No newline at end of file diff --git a/docker/local/docker-compose.yml b/docker/local/docker-compose.yml new file mode 100644 index 0000000..4ffe0b5 --- /dev/null +++ b/docker/local/docker-compose.yml @@ -0,0 +1,71 @@ +# Base services for Pulp Manager +version: '3.8' + +services: + mariadb: + image: mariadb:11.1.2-jammy + ports: + - "3306:3306" + environment: + MARIADB_USER: pulp-manager + MARIADB_PASSWORD: pulp-manager + MARIADB_ROOT_PASSWORD: my-root-password + MARIADB_DATABASE: pulp_manager + networks: + - pulp-net + + redis-manager: + image: redis:latest + ports: + - "6379:6379" + networks: + - pulp-net + + pulp-manager-api: + build: ../.. + entrypoint: ["/bin/sh", "-c"] + command: ["/usr/local/bin/pulp-manager -m && /usr/local/bin/pulp-manager -a --no-ssl --dev && /usr/local/bin/pulp-manager -w"] + volumes: + - ../..:/pulp_manager + ports: + - "8080:8000" + environment: + DB_HOSTNAME: mariadb + DB_NAME: pulp_manager + DB_USER: pulp-manager + DB_PASSWORD: pulp-manager + JWT_SECRET: test_secret + PULP_MANAGER_CONFIG_PATH: /pulp_manager/docker/local/config.ini + PULP_SYNC_CONFIG_PATH: /pulp_manager/docker/local/pulp-config.yml + Is_local: true + depends_on: + - mariadb + - redis-manager + networks: + - pulp-net + + pulp-python-worker: + build: ../.. + entrypoint: ["/bin/sh", "-c"] + command: ["/usr/local/bin/pulp-manager -w"] + volumes: + - ../..:/pulp_manager + environment: + DB_HOSTNAME: mariadb + DB_NAME: pulp_manager + DB_USER: pulp-manager + DB_PASSWORD: pulp-manager + JWT_SECRET: test_secret + PULP_MANAGER_CONFIG_PATH: /pulp_manager/docker/local/config.ini + PULP_SYNC_CONFIG_PATH: /pulp_manager/docker/local/pulp-config.yml + Is_local: true + depends_on: + - mariadb + - redis-manager + - pulp-manager-api + networks: + - pulp-net + +networks: + pulp-net: + external: true \ No newline at end of file diff --git a/docker/local/pulp-config.yml b/docker/local/pulp-config.yml new file mode 100644 index 0000000..ab51c5d --- /dev/null +++ b/docker/local/pulp-config.yml @@ -0,0 +1,35 @@ +pulp_servers: + # Primary Pulp instance + pulp-web-primary:8080: + credentials: local + repo_config_registration: + schedule: "0,15,30,45 * * * *" + max_runtime: "20m" + repo_groups: + external_repos: + schedule: "00 02 * * *" + max_concurrent_syncs: 2 + max_runtime: "6h" + snapshot_support: + max_concurrent_snapshots: 2 + + # Secondary Pulp instance - syncs from primary + pulp-web-secondary:8080: + credentials: local + repo_groups: + external_repos: + schedule: "00 06 * * *" + max_concurrent_syncs: 2 + max_runtime: "6h" + pulp_master: pulp-web-primary:8080 + +credentials: + local: + username: admin + vault_service_account_mount: service-accounts # optional: Password is loaded from ini file. + +repo_groups: + external_repos: + regex_include: "^ext-" + internal_repos: + regex_include: "^int-" \ No newline at end of file diff --git a/docker/local/pulp-primary.yml b/docker/local/pulp-primary.yml new file mode 100644 index 0000000..b8529f1 --- /dev/null +++ b/docker/local/pulp-primary.yml @@ -0,0 +1,215 @@ +# Primary Pulp3 instance +version: '3.8' + +services: + postgres-primary: + image: "docker.io/library/postgres:13" + ports: + - "5432:5432" + environment: + POSTGRES_USER: pulp + POSTGRES_PASSWORD: password + POSTGRES_DB: pulp_primary + POSTGRES_INITDB_ARGS: '--auth-host=scram-sha-256' + POSTGRES_HOST_AUTH_METHOD: 'scram-sha-256' + volumes: + - "pg_data_primary:/var/lib/postgresql/data" + restart: always + healthcheck: + test: pg_isready -U pulp -d pulp_primary + interval: 10s + timeout: 5s + retries: 5 + networks: + - pulp-net + + redis-primary: + image: "docker.io/library/redis:latest" + volumes: + - "redis_data_primary:/data" + restart: always + healthcheck: + test: redis-cli ping + interval: 10s + timeout: 5s + retries: 5 + networks: + - pulp-net + + migration-primary: + image: "pulp/pulp-minimal:3.49.1" + depends_on: + postgres-primary: + condition: service_healthy + command: pulpcore-manager migrate --noinput + volumes: + - "../../assets/settings_primary.py:/etc/pulp/settings.py:z" + - "../../assets/certs:/etc/pulp/certs:z" + - "../../assets/keys:/etc/pulp/keys:z" + - "pulp_primary:/var/lib/pulp" + environment: + POSTGRES_HOST: postgres-primary + REDIS_HOST: redis-primary + networks: + - pulp-net + + signing-key-primary: + image: "pulp/pulp-minimal:3.49.1" + command: sh -c "add_signing_service.sh" + depends_on: + postgres-primary: + condition: service_healthy + migration-primary: + condition: service_completed_successfully + environment: + PULP_SIGNING_KEY_FINGERPRINT: '' + POSTGRES_HOST: postgres-primary + REDIS_HOST: redis-primary + volumes: + - "../../assets/settings_primary.py:/etc/pulp/settings.py:z" + - "../../assets/certs:/etc/pulp/certs:z" + - "../../assets/keys:/etc/pulp/keys:z" + - "pulp_primary:/var/lib/pulp" + networks: + - pulp-net + + init-password-primary: + image: "pulp/pulp-minimal:3.49.1" + command: set_init_password.sh + depends_on: + postgres-primary: + condition: service_healthy + environment: + PULP_DEFAULT_ADMIN_PASSWORD: password + POSTGRES_HOST: postgres-primary + REDIS_HOST: redis-primary + volumes: + - "../../assets/settings_primary.py:/etc/pulp/settings.py:z" + - "../../assets/certs:/etc/pulp/certs:z" + - "../../assets/keys:/etc/pulp/keys:z" + - "pulp_primary:/var/lib/pulp" + networks: + - pulp-net + + pulp-web-primary: + image: "pulp/pulp-web:3.49.1" + command: ['nginx', '-g', 'daemon off;'] + depends_on: + pulp-api-primary: + condition: service_healthy + pulp-content-primary: + condition: service_healthy + ports: + - "8000:8080" + hostname: pulp-web-primary + user: root + volumes: + - "../../assets/nginx-conf/nginx-primary.conf:/etc/nginx/nginx.conf:Z" + restart: always + networks: + - pulp-net + + pulp-api-primary: + image: "pulp/pulp-minimal:3.49.1" + deploy: + replicas: 2 + command: ['pulp-api'] + depends_on: + redis-primary: + condition: service_healthy + postgres-primary: + condition: service_healthy + migration-primary: + condition: service_completed_successfully + init-password-primary: + condition: service_completed_successfully + signing-key-primary: + condition: service_completed_successfully + hostname: pulp-api-primary + user: pulp + environment: + PULP_SETTINGS: /etc/pulp/settings.py + PULP_ALLOWED_HOSTS: '["*"]' + POSTGRES_HOST: postgres-primary + REDIS_HOST: redis-primary + volumes: + - "../../assets/settings_primary.py:/etc/pulp/settings.py:z" + - "../../assets/certs:/etc/pulp/certs:z" + - "../../assets/keys:/etc/pulp/keys:z" + - "pulp_primary:/var/lib/pulp" + restart: always + healthcheck: + test: readyz.py /pulp/api/v3/status/ + interval: 10s + timeout: 5s + retries: 5 + networks: + - pulp-net + + pulp-content-primary: + image: "pulp/pulp-minimal:3.49.1" + deploy: + replicas: 2 + command: ['pulp-content'] + depends_on: + redis-primary: + condition: service_healthy + postgres-primary: + condition: service_healthy + migration-primary: + condition: service_completed_successfully + hostname: pulp-content-primary + user: pulp + environment: + POSTGRES_HOST: postgres-primary + REDIS_HOST: redis-primary + volumes: + - "../../assets/settings_primary.py:/etc/pulp/settings.py:z" + - "../../assets/certs:/etc/pulp/certs:z" + - "../../assets/keys:/etc/pulp/keys:z" + - "pulp_primary:/var/lib/pulp" + restart: always + healthcheck: + test: readyz.py /pulp/content/ + interval: 10s + timeout: 5s + retries: 5 + networks: + - pulp-net + + pulp-worker-primary: + image: "pulp/pulp-minimal:3.49.1" + deploy: + replicas: 2 + command: ['pulp-worker'] + depends_on: + redis-primary: + condition: service_healthy + postgres-primary: + condition: service_healthy + migration-primary: + condition: service_completed_successfully + user: pulp + environment: + POSTGRES_HOST: postgres-primary + REDIS_HOST: redis-primary + volumes: + - "../../assets/settings_primary.py:/etc/pulp/settings.py:z" + - "../../assets/certs:/etc/pulp/certs:z" + - "../../assets/keys:/etc/pulp/keys:z" + - "pulp_primary:/var/lib/pulp" + restart: always + networks: + - pulp-net + +volumes: + pulp_primary: + name: pulp_primary_local + pg_data_primary: + name: pg_data_primary_local + redis_data_primary: + name: redis_data_primary_local + +networks: + pulp-net: + external: true \ No newline at end of file diff --git a/docker/local/pulp-secondary.yml b/docker/local/pulp-secondary.yml new file mode 100644 index 0000000..b8cbe0b --- /dev/null +++ b/docker/local/pulp-secondary.yml @@ -0,0 +1,215 @@ +# Secondary Pulp3 instance +version: '3.8' + +services: + postgres-secondary: + image: "docker.io/library/postgres:13" + ports: + - "5433:5432" + environment: + POSTGRES_USER: pulp + POSTGRES_PASSWORD: password + POSTGRES_DB: pulp_secondary + POSTGRES_INITDB_ARGS: '--auth-host=scram-sha-256' + POSTGRES_HOST_AUTH_METHOD: 'scram-sha-256' + volumes: + - "pg_data_secondary:/var/lib/postgresql/data" + restart: always + healthcheck: + test: pg_isready -U pulp -d pulp_secondary + interval: 10s + timeout: 5s + retries: 5 + networks: + - pulp-net + + redis-secondary: + image: "docker.io/library/redis:latest" + volumes: + - "redis_data_secondary:/data" + restart: always + healthcheck: + test: redis-cli ping + interval: 10s + timeout: 5s + retries: 5 + networks: + - pulp-net + + migration-secondary: + image: "pulp/pulp-minimal:3.49.1" + depends_on: + postgres-secondary: + condition: service_healthy + command: pulpcore-manager migrate --noinput + volumes: + - "../../assets/settings_secondary.py:/etc/pulp/settings.py:z" + - "../../assets/certs:/etc/pulp/certs:z" + - "../../assets/keys:/etc/pulp/keys:z" + - "pulp_secondary:/var/lib/pulp" + environment: + POSTGRES_HOST: postgres-secondary + REDIS_HOST: redis-secondary + networks: + - pulp-net + + signing-key-secondary: + image: "pulp/pulp-minimal:3.49.1" + command: sh -c "add_signing_service.sh" + depends_on: + postgres-secondary: + condition: service_healthy + migration-secondary: + condition: service_completed_successfully + environment: + PULP_SIGNING_KEY_FINGERPRINT: '' + POSTGRES_HOST: postgres-secondary + REDIS_HOST: redis-secondary + volumes: + - "../../assets/settings_secondary.py:/etc/pulp/settings.py:z" + - "../../assets/certs:/etc/pulp/certs:z" + - "../../assets/keys:/etc/pulp/keys:z" + - "pulp_secondary:/var/lib/pulp" + networks: + - pulp-net + + init-password-secondary: + image: "pulp/pulp-minimal:3.49.1" + command: set_init_password.sh + depends_on: + postgres-secondary: + condition: service_healthy + environment: + PULP_DEFAULT_ADMIN_PASSWORD: password + POSTGRES_HOST: postgres-secondary + REDIS_HOST: redis-secondary + volumes: + - "../../assets/settings_secondary.py:/etc/pulp/settings.py:z" + - "../../assets/certs:/etc/pulp/certs:z" + - "../../assets/keys:/etc/pulp/keys:z" + - "pulp_secondary:/var/lib/pulp" + networks: + - pulp-net + + pulp-web-secondary: + image: "pulp/pulp-web:3.49.1" + command: ['nginx', '-g', 'daemon off;'] + depends_on: + pulp-api-secondary: + condition: service_healthy + pulp-content-secondary: + condition: service_healthy + ports: + - "8001:8080" + hostname: pulp-web-secondary + user: root + volumes: + - "../../assets/nginx-conf/nginx-secondary.conf:/etc/nginx/nginx.conf:Z" + restart: always + networks: + - pulp-net + + pulp-api-secondary: + image: "pulp/pulp-minimal:3.49.1" + deploy: + replicas: 2 + command: ['pulp-api'] + depends_on: + redis-secondary: + condition: service_healthy + postgres-secondary: + condition: service_healthy + migration-secondary: + condition: service_completed_successfully + init-password-secondary: + condition: service_completed_successfully + signing-key-secondary: + condition: service_completed_successfully + hostname: pulp-api-secondary + user: pulp + environment: + PULP_SETTINGS: /etc/pulp/settings.py + PULP_ALLOWED_HOSTS: '["*"]' + POSTGRES_HOST: postgres-secondary + REDIS_HOST: redis-secondary + volumes: + - "../../assets/settings_secondary.py:/etc/pulp/settings.py:z" + - "../../assets/certs:/etc/pulp/certs:z" + - "../../assets/keys:/etc/pulp/keys:z" + - "pulp_secondary:/var/lib/pulp" + restart: always + healthcheck: + test: readyz.py /pulp/api/v3/status/ + interval: 10s + timeout: 5s + retries: 5 + networks: + - pulp-net + + pulp-content-secondary: + image: "pulp/pulp-minimal:3.49.1" + deploy: + replicas: 2 + command: ['pulp-content'] + depends_on: + redis-secondary: + condition: service_healthy + postgres-secondary: + condition: service_healthy + migration-secondary: + condition: service_completed_successfully + hostname: pulp-content-secondary + user: pulp + environment: + POSTGRES_HOST: postgres-secondary + REDIS_HOST: redis-secondary + volumes: + - "../../assets/settings_secondary.py:/etc/pulp/settings.py:z" + - "../../assets/certs:/etc/pulp/certs:z" + - "../../assets/keys:/etc/pulp/keys:z" + - "pulp_secondary:/var/lib/pulp" + restart: always + healthcheck: + test: readyz.py /pulp/content/ + interval: 10s + timeout: 5s + retries: 5 + networks: + - pulp-net + + pulp-worker-secondary: + image: "pulp/pulp-minimal:3.49.1" + deploy: + replicas: 2 + command: ['pulp-worker'] + depends_on: + redis-secondary: + condition: service_healthy + postgres-secondary: + condition: service_healthy + migration-secondary: + condition: service_completed_successfully + user: pulp + environment: + POSTGRES_HOST: postgres-secondary + REDIS_HOST: redis-secondary + volumes: + - "../../assets/settings_secondary.py:/etc/pulp/settings.py:z" + - "../../assets/certs:/etc/pulp/certs:z" + - "../../assets/keys:/etc/pulp/keys:z" + - "pulp_secondary:/var/lib/pulp" + restart: always + networks: + - pulp-net + +volumes: + pulp_secondary: + name: pulp_secondary_local + pg_data_secondary: + name: pg_data_secondary_local + redis_data_secondary: + name: redis_data_secondary_local + +networks: + pulp-net: + external: true \ No newline at end of file diff --git a/dockercompose-pulp3.yml b/docker/pulp3.yml similarity index 100% rename from dockercompose-pulp3.yml rename to docker/pulp3.yml diff --git a/dockercompose-tests.yml b/dockercompose-tests.yml deleted file mode 100644 index fcafeb0..0000000 --- a/dockercompose-tests.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: "3.8" - -services: - webapp: - build: - context: ./ - dockerfile: Dockerfile - command: sh -c "./wait_db.sh && coverage erase && make test && make cover" - restart: "no" - environment: - DB_HOSTNAME: db - DB_NAME: pulp_manager - DB_USER: pulp-manager - DB_PASSWORD: pulp-manager - PULP_MANAGER_SKIP_PARSER_CONFIG: 1 - PULP_MANAGER_CONFIG_PATH: /pulp_manager/local_config.ini - depends_on: - - db - - db: - image: mariadb:11.1.2-jammy - restart: "no" - environment: - MARIADB_USER: pulp-manager - MARIADB_PASSWORD: pulp-manager - MARIADB_ROOT_PASSWORD: my-root-password - MARIADB_DATABASE: pulp_manager - healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] - interval: 30s - timeout: 10s - retries: 3 diff --git a/requirements.txt b/requirements.txt index 16390de..4f7ef18 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ dill==0.3.7 docker==7.0.0 exceptiongroup==1.1.3 fakeredis==2.20.0 -fastapi==0.104.0 +fastapi==0.104.1 freezegun==1.2.2 gitdb==4.0.11 GitPython==3.1.40 @@ -39,7 +39,7 @@ prometheus-client==0.19.0 pyasn1==0.5.1 pyasn1-modules==0.3.0 pycparser==2.21 -pydantic==1.9.2 +pydantic==1.10.13 pyhcl==0.4.5 PyJWT==2.8.0 pylint==3.0.0 From 659f951d76c845afe3db0764f7c65ac69c8614dc Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Wed, 10 Sep 2025 12:53:39 -0400 Subject: [PATCH 02/24] removed redundant compose files Signed-off-by: Geoff Wilson --- Makefile | 4 +- docker/docker-compose.yml | 70 -------------- docker/pulp3.yml | 194 -------------------------------------- 3 files changed, 2 insertions(+), 266 deletions(-) delete mode 100644 docker/docker-compose.yml delete mode 100644 docker/pulp3.yml diff --git a/Makefile b/Makefile index bd30fe0..2faebf6 100644 --- a/Makefile +++ b/Makefile @@ -43,12 +43,12 @@ venv: requirements.txt run-pulp-manager: setup-network @echo "Starting local Docker Compose environment..." - docker compose -f docker/docker-compose.yml up --build + docker compose -f docker/local/docker-compose.yml up --build .PHONY : run-pulp3 run-pulp3: setup-network setup-pulp-keys @echo "Starting Pulp 3 locally with Docker Compose..." - docker compose -f docker/pulp3.yml up --build + docker compose -f docker/local/pulp-primary.yml up --build .PHONY : run-cluster run-cluster: setup-network setup-pulp-keys diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index dde10c1..0000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,70 +0,0 @@ -version: '3.8' - -services: - mariadb: - image: mariadb:11.1.2-jammy - ports: - - "3306:3306" - environment: - MARIADB_USER: pulp-manager - MARIADB_PASSWORD: pulp-manager - MARIADB_ROOT_PASSWORD: my-root-password - MARIADB_DATABASE: pulp_manager - networks: - - pulp-net - - redis: - image: redis:latest - networks: - - pulp-net - - pulp-manager-api: - build: . - entrypoint: ["/bin/sh", "-c"] - command: ["/usr/local/bin/pulp-manager -m && /usr/local/bin/pulp-manager -a --no-ssl --dev && /usr/local/bin/pulp-manager -w"] - volumes: - - .:/pulp_manager - ports: - - "8080:8000" - environment: - DB_HOSTNAME: mariadb - DB_NAME: pulp_manager - DB_USER: pulp-manager - DB_PASSWORD: pulp-manager - JWT_SECRET: test_secret - PULP_MANAGER_CONFIG_PATH: /pulp_manager/local_config.ini - PULP_SYNC_CONFIG_PATH: /pulp_manager/local_pulp_config.yml - #PULP_MANAGER_SKIP_PARSER_CONFIG: 1 - Is_local: true - depends_on: - - mariadb - - redis - networks: - - pulp-net - - pulp-python-worker: - build: . - entrypoint: ["/bin/sh", "-c"] - command: ["/usr/local/bin/pulp-manager -w"] - volumes: - - .:/pulp_manager - environment: - DB_HOSTNAME: mariadb - DB_NAME: pulp_manager - DB_USER: pulp-manager - DB_PASSWORD: pulp-manager - JWT_SECRET: test_secret - PULP_MANAGER_CONFIG_PATH: /pulp_manager/local_config.ini - PULP_SYNC_CONFIG_PATH: /pulp_manager/local_pulp_config.yml - #PULP_MANAGER_SKIP_PARSER_CONFIG: 1 - Is_local: true - depends_on: - - mariadb - - redis - - pulp-manager-api - networks: - - pulp-net - -networks: - pulp-net: - external: true diff --git a/docker/pulp3.yml b/docker/pulp3.yml deleted file mode 100644 index 4a4d75b..0000000 --- a/docker/pulp3.yml +++ /dev/null @@ -1,194 +0,0 @@ -version: '3' -services: - postgres: - image: "docker.io/library/postgres:13" - ports: - - "5432:5432" - environment: - POSTGRES_USER: pulp - POSTGRES_PASSWORD: password - POSTGRES_DB: pulp - POSTGRES_INITDB_ARGS: '--auth-host=scram-sha-256' - POSTGRES_HOST_AUTH_METHOD: 'scram-sha-256' - volumes: - - "pg_data:/var/lib/postgresql/data" - - "./assets/postgres/passwd:/etc/passwd:Z" - restart: always - healthcheck: - test: pg_isready -U pulp - interval: 10s - timeout: 5s - retries: 5 - networks: - - pulp-net - - redis-pulp: - image: "docker.io/library/redis:latest" - volumes: - - "redis_data:/data" - restart: always - healthcheck: - test: redis-cli ping - interval: 10s - timeout: 5s - retries: 5 - networks: - - pulp-net - - migration_service: - image: "pulp/pulp-minimal:3.49.1" - depends_on: - postgres: - condition: service_healthy - command: pulpcore-manager migrate --noinput - volumes: - - "./assets/settings.py:/etc/pulp/settings.py:z" - - "./assets/certs:/etc/pulp/certs:z" - - "pulp:/var/lib/pulp" - networks: - - pulp-net - - signing_key_service: - image: "pulp/pulp-minimal:3.49.1" - command: sh -c "add_signing_service.sh" - depends_on: - postgres: - condition: service_healthy - migration_service: - condition: service_completed_successfully - environment: - PULP_SIGNING_KEY_FINGERPRINT: '' - volumes: - - "./assets/settings.py:/etc/pulp/settings.py:z" - - "./assets/certs:/etc/pulp/certs:z" - - "pulp:/var/lib/pulp" - networks: - - pulp-net - - set_init_password_service: - image: "pulp/pulp-minimal:3.49.1" - command: set_init_password.sh - depends_on: - postgres: - condition: service_healthy - environment: - PULP_DEFAULT_ADMIN_PASSWORD: password - volumes: - - "./assets/settings.py:/etc/pulp/settings.py:z" - - "./assets/certs:/etc/pulp/certs:z" - - "pulp:/var/lib/pulp" - networks: - - pulp-net - - pulp-web: - image: "pulp/pulp-web:3.49.1" - command: ['/usr/bin/nginx.sh'] - depends_on: - pulp_api: - condition: service_healthy - pulp_content: - condition: service_healthy - ports: - - "8000:8080" - hostname: pulp_web:8080 - user: root - volumes: - - "./assets/bin/nginx.sh:/usr/bin/nginx.sh:Z" - - "./assets/nginx/nginx.conf.template:/etc/opt/rh/rh-nginx116/nginx/nginx.conf.template:Z" - restart: always - networks: - - pulp-net - - pulp_api: - image: "pulp/pulp-minimal:3.49.1" - deploy: - replicas: 2 - command: ['pulp-api'] - depends_on: - redis-pulp: - condition: service_healthy - postgres: - condition: service_healthy - migration_service: - condition: service_completed_successfully - set_init_password_service: - condition: service_completed_successfully - signing_key_service: - condition: service_completed_successfully - hostname: pulp-api - user: pulp - environment: - PULP_SETTINGS: /etc/pulp/settings.py - PULP_ALLOWED_HOSTS: '["*"]' - volumes: - - "./assets/settings.py:/etc/pulp/settings.py:z" - - "./assets/certs:/etc/pulp/certs:z" - - "pulp:/var/lib/pulp" - restart: always - healthcheck: - test: readyz.py /pulp/api/v3/status/ - interval: 10s - timeout: 5s - retries: 5 - networks: - - pulp-net - - pulp_content: - image: "pulp/pulp-minimal:3.49.1" - deploy: - replicas: 2 - command: ['pulp-content'] - depends_on: - redis-pulp: - condition: service_healthy - postgres: - condition: service_healthy - migration_service: - condition: service_completed_successfully - hostname: pulp-content - user: pulp - volumes: - - "./assets/settings.py:/etc/pulp/settings.py:z" - - "./assets/certs:/etc/pulp/certs:z" - - "pulp:/var/lib/pulp" - restart: always - healthcheck: - test: readyz.py /pulp/content/ - interval: 10s - timeout: 5s - retries: 5 - networks: - - pulp-net - - pulp_worker: - image: "pulp/pulp-minimal:3.49.1" - deploy: - replicas: 2 - command: ['pulp-worker'] - depends_on: - redis-pulp: - condition: service_healthy - postgres: - condition: service_healthy - migration_service: - condition: service_completed_successfully - user: pulp - volumes: - - "./assets/settings.py:/etc/pulp/settings.py:z" - - "./assets/certs:/etc/pulp/certs:z" - - "pulp:/var/lib/pulp" - restart: always - networks: - - pulp-net - -volumes: - pulp: - name: pulp${DEV_VOLUME_SUFFIX:-dev} - pg_data: - name: pg_data${DEV_VOLUME_SUFFIX:-dev} - redis_data: - name: redis_data${DEV_VOLUME_SUFFIX:-dev} - -networks: - pulp-net: - external: true From 5685edc05bdb4be89496c84e0b1e656880d671b5 Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Mon, 15 Sep 2025 11:41:31 -0400 Subject: [PATCH 03/24] Refactor local to single compose file (simple cluster) --- .gitignore | 24 ++ Makefile | 51 +++-- README.md | 3 + assets/packages/hello_2.10-2_amd64.deb | Bin 0 -> 56132 bytes assets/packages/nano_7.2-1_amd64.deb | 0 assets/scripts/deb_sign.sh | 37 +++ assets/scripts/signing_service.py | 125 ++++++++++ docker/local/config.ini | 43 ---- docker/local/docker-compose.yml | 71 ------ docker/local/pulp-config.yml | 35 --- docker/local/pulp-primary.yml | 215 ------------------ docker/local/pulp-secondary.yml | 215 ------------------ docker/simple-cluster.yml | 131 +++++++++++ docker/simple/config.ini | 34 +++ docker/simple/pulp-config.yml | 23 ++ .../simple/pulp-primary/containers/.gitkeep | 0 .../simple/pulp-secondary/containers/.gitkeep | 0 docker/simple/settings/settings.py | 65 ++++++ .../app/repositories/table_repository.py | 17 +- pulp_manager/app/services/pulp_manager.py | 34 ++- upload-package.sh | 53 +++++ 21 files changed, 572 insertions(+), 604 deletions(-) create mode 100644 assets/packages/hello_2.10-2_amd64.deb create mode 100644 assets/packages/nano_7.2-1_amd64.deb create mode 100755 assets/scripts/deb_sign.sh create mode 100644 assets/scripts/signing_service.py delete mode 100644 docker/local/config.ini delete mode 100644 docker/local/docker-compose.yml delete mode 100644 docker/local/pulp-config.yml delete mode 100644 docker/local/pulp-primary.yml delete mode 100644 docker/local/pulp-secondary.yml create mode 100644 docker/simple-cluster.yml create mode 100644 docker/simple/config.ini create mode 100644 docker/simple/pulp-config.yml create mode 100644 docker/simple/pulp-primary/containers/.gitkeep create mode 100644 docker/simple/pulp-secondary/containers/.gitkeep create mode 100644 docker/simple/settings/settings.py create mode 100755 upload-package.sh diff --git a/.gitignore b/.gitignore index 82adb58..e7bd70f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,26 @@ __pycache__ venv + +# Runtime data in docker/simple directories +docker/simple/pulp-primary/storage/* +docker/simple/pulp-primary/pgsql/* +docker/simple/pulp-primary/containers/* +docker/simple/pulp-secondary/storage/* +docker/simple/pulp-secondary/pgsql/* +docker/simple/pulp-secondary/containers/* +docker/simple/settings/certs/* + +# But keep the directories themselves +!docker/simple/pulp-primary/storage/.gitkeep +!docker/simple/pulp-primary/pgsql/.gitkeep +!docker/simple/pulp-primary/containers/.gitkeep +!docker/simple/pulp-secondary/storage/.gitkeep +!docker/simple/pulp-secondary/pgsql/.gitkeep +!docker/simple/pulp-secondary/containers/.gitkeep +!docker/simple/settings/certs/.gitkeep + +# Other runtime files +assets/certs/ +assets/keys/ +.coverage +.claude/ diff --git a/Makefile b/Makefile index 2faebf6..c935823 100644 --- a/Makefile +++ b/Makefile @@ -15,9 +15,11 @@ h help: "venv" "Create virtualenv" \ "bdist" "Create wheel file" \ "clean" "Clean workspace" \ + "setup-keys" "Generates keys for use in local cluster" \ "run-pulp3" "Start Pulp 3 locally with Docker Compose" \ - "run-pulp-manager" "Start Pulp Manager with Docker Compose" \ - "run-cluster" "Start Pulp3 + Pulp Manger cluster with Docker Compose" + "run-pulp-manager" "Start Pulp Manager with Docker Compose" \ + "run-cluster" "Start Pulp3 + Pulp Manger local cluster with Docker Compose" \ + "upload-demo-package" "Upload demo deb package to local cluster pulp3-primary" .PHONY : l lint l lint: venv @@ -41,21 +43,19 @@ venv: requirements.txt pip install --upgrade pip; \ pip install -r requirements.txt -run-pulp-manager: setup-network - @echo "Starting local Docker Compose environment..." - docker compose -f docker/local/docker-compose.yml up --build +run-pulp-manager: setup-network setup-keys + @echo "Starting Pulp Manager services..." + docker compose -f docker/simple-cluster.yml up mariadb redis-manager pulp-manager-api pulp-manager-worker pulp-manager-scheduler --build .PHONY : run-pulp3 -run-pulp3: setup-network setup-pulp-keys - @echo "Starting Pulp 3 locally with Docker Compose..." - docker compose -f docker/local/pulp-primary.yml up --build +run-pulp3: setup-network + @echo "Starting simplified Pulp 3 primary and secondary..." + docker compose -f docker/simple-cluster.yml up pulp-primary pulp-secondary --build .PHONY : run-cluster -run-cluster: setup-network setup-pulp-keys - @echo "Starting complete local cluster with Pulp Manager, Primary and Secondary Pulp instances..." - docker compose -f docker/local/docker-compose.yml \ - -f docker/local/pulp-primary.yml \ - -f docker/local/pulp-secondary.yml up --build +run-cluster: setup-network setup-keys + @echo "Starting complete simplified cluster with Pulp Manager, Primary and Secondary Pulp instances..." + docker compose -f docker/simple-cluster.yml up --build setup-network: @echo "Creating or verifying network..." @@ -63,7 +63,7 @@ setup-network: docker network create pulp-net @echo "Network setup completed." -setup-pulp-keys: +setup-keys: @echo "Checking for Pulp encryption keys..." @mkdir -p assets/certs assets/keys assets/nginx-conf @if [ ! -f assets/certs/database_fields.symmetric.key ]; then \ @@ -81,3 +81,26 @@ setup-pulp-keys: else \ echo "Container auth keys already exist."; \ fi + @mkdir -p assets/keys/gpg + @if [ ! -f assets/keys/gpg/secring.gpg ]; then \ + echo "Generating GPG signing keys..."; \ + chmod 700 assets/keys/gpg; \ + echo "Key-Type: RSA" > /tmp/gpg-batch-config; \ + echo "Key-Length: 2048" >> /tmp/gpg-batch-config; \ + echo "Name-Real: Demo Signing Service" >> /tmp/gpg-batch-config; \ + echo "Name-Email: signing@pulp-demo.local" >> /tmp/gpg-batch-config; \ + echo "Expire-Date: 0" >> /tmp/gpg-batch-config; \ + echo "%no-protection" >> /tmp/gpg-batch-config; \ + echo "%commit" >> /tmp/gpg-batch-config; \ + GNUPGHOME=assets/keys/gpg gpg --batch --gen-key /tmp/gpg-batch-config; \ + GNUPGHOME=assets/keys/gpg gpg --armor --export > assets/keys/gpg/public.key; \ + rm /tmp/gpg-batch-config; \ + echo "GPG signing keys created."; \ + else \ + echo "GPG signing keys already exist."; \ + fi + +.PHONY : upload-demo-package +upload-demo-package: + @echo "Uploading demo package to pulp3-primary..." + @./upload-package.sh diff --git a/README.md b/README.md index 516e78b..a9b548c 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,7 @@ internal_package_prefix=corp_ package_name_replacement_pattern= package_name_replacement_rule= remote_tls_validation=true +use_https_for_sync=true [redis] host=redis @@ -239,6 +240,8 @@ Settings to apply to all pulp servers the pulp repo config - `remote_tls_validation`: Boolean whether to require TLS validation of remote hosts +- `use_https_for_sync`: Boolean whether to use HTTPS for repository sync URLs. + Set to false for local HTTP-only development environments. Defaults to true. ### redis diff --git a/assets/packages/hello_2.10-2_amd64.deb b/assets/packages/hello_2.10-2_amd64.deb new file mode 100644 index 0000000000000000000000000000000000000000..f486c415f4a277c747307a1acbb5320ab1495518 GIT binary patch literal 56132 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 "$OUTPUT_FILE" + # Return JSON with the correct signature file path for Pulp + KEY_ID=$(echo "$RESPONSE" | python3 -c "import json, sys; data=json.load(sys.stdin); print(data.get('key_id', ''))") + echo "{\"signature\": \"$OUTPUT_FILE\", \"status\": \"success\", \"key_id\": \"$KEY_ID\"}" +else + # Fallback: copy input to output and return error + cp "$1" "$OUTPUT_FILE" + echo '{"error": "Signing service failed", "status": "failed"}' + exit 1 +fi \ No newline at end of file diff --git a/assets/scripts/signing_service.py b/assets/scripts/signing_service.py new file mode 100644 index 0000000..aaeb4f7 --- /dev/null +++ b/assets/scripts/signing_service.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 + +import os +import json +import tempfile +import subprocess +from flask import Flask, request, jsonify +from pathlib import Path + +app = Flask(__name__) + +# Initialize GPG on startup +def init_gpg(): + """Initialize GPG with mounted keyring""" + gnupg_home = "/app/gpg" + + # Set GPG home to the mounted directory + os.environ['GNUPGHOME'] = gnupg_home + + if not os.path.exists(gnupg_home): + raise Exception(f"GPG keyring directory {gnupg_home} not found. Please mount the GPG keyring.") + + # Check if key exists + result = subprocess.run(['gpg', '--list-secret-keys'], + capture_output=True, text=True) + + if result.returncode != 0 or not result.stdout.strip(): + raise Exception("No GPG secret key found in mounted keyring") + + print("Using mounted GPG keyring") + + # Get the key ID + result = subprocess.run(['gpg', '--list-secret-keys', '--with-colons'], + capture_output=True, text=True, check=True) + + for line in result.stdout.split('\n'): + if line.startswith('sec:'): + return line.split(':')[4] + + raise Exception("No GPG key found") + +@app.route('/sign', methods=['POST']) +def sign_file(): + """Sign a file and return signature information""" + try: + # Get the file from request + if 'file' not in request.files: + return jsonify({'error': 'No file provided'}), 400 + + file = request.files['file'] + if file.filename == '': + return jsonify({'error': 'No file selected'}), 400 + + # Save file to temp location + with tempfile.NamedTemporaryFile(delete=False, suffix='.deb') as temp_file: + file.save(temp_file.name) + input_file = temp_file.name + + # Create signature file + signature_file = input_file + '.asc' + + try: + # Sign the file with GPG + subprocess.run([ + 'gpg', '--detach-sign', '--armor', + '--output', signature_file, + input_file + ], check=True, capture_output=True) + + # Read the signature + with open(signature_file, 'r') as f: + signature_content = f.read() + + return jsonify({ + 'signature': signature_file, + 'signature_content': signature_content, + 'status': 'success', + 'key_id': app.config['KEY_ID'] + }) + + finally: + # Clean up temp files + if os.path.exists(input_file): + os.unlink(input_file) + + except Exception as e: + return jsonify({'error': str(e), 'status': 'failed'}), 500 + +@app.route('/health', methods=['GET']) +def health(): + """Health check endpoint""" + return jsonify({'status': 'healthy', 'key_id': app.config.get('KEY_ID', 'unknown')}) + +@app.route('/public-key', methods=['GET']) +def get_public_key(): + """Export the public key""" + try: + key_id = app.config.get('KEY_ID') + if not key_id: + return jsonify({'error': 'No key available'}), 500 + + # Export the public key + result = subprocess.run([ + 'gpg', '--armor', '--export', key_id + ], capture_output=True, text=True, check=True) + + return jsonify({ + 'key_id': key_id, + 'public_key': result.stdout, + 'status': 'success' + }) + + except Exception as e: + return jsonify({'error': str(e), 'status': 'failed'}), 500 + +if __name__ == '__main__': + print("Initializing signing service...") + try: + key_id = init_gpg() + app.config['KEY_ID'] = key_id + print(f"Signing service ready with key ID: {key_id}") + app.run(host='0.0.0.0', port=8080, debug=False) + except Exception as e: + print(f"Failed to initialize signing service: {e}") + exit(1) \ No newline at end of file diff --git a/docker/local/config.ini b/docker/local/config.ini deleted file mode 100644 index 1a35a5d..0000000 --- a/docker/local/config.ini +++ /dev/null @@ -1,43 +0,0 @@ -;[ca] -;root_ca_file_path="" - -[auth] -method=ldap -use_ssl=true -ldap_servers=dc.example.com -base_dn=DC=example,DC=com -default_domain=example.com -jwt_algorithm=HS256 -jwt_token_lifetime_mins=480 -admin_group=pulpmaster-rw - -[pulp] -deb_signing_service=pulp_deb -banned_package_regex=bannedexample|another -internal_domains=pulp-web-primary,pulp-web-secondary -git_repo_config=https://git.example.com/Pulp-Repo-Config -git_repo_config_dir=repo_config -password=password -internal_package_prefix=int_ -package_name_replacement_pattern= -package_name_replacement_rule= -;may need to be false if using http proxy to remote -remote_tls_validation=false - -[redis] -host=redis-manager -port=6379 -db=0 -max_page_size=24 - -[remotes] -sock_connect_timeout=120.0 -sock_read_timeout=600.0 - -[paging] -default_page_size=50 -max_page_size=20000 - -[vault] -vault_addr=http://127.0.0.1:8200 -repo_secret_namespace=cle-secrets-common-dev \ No newline at end of file diff --git a/docker/local/docker-compose.yml b/docker/local/docker-compose.yml deleted file mode 100644 index 4ffe0b5..0000000 --- a/docker/local/docker-compose.yml +++ /dev/null @@ -1,71 +0,0 @@ -# Base services for Pulp Manager -version: '3.8' - -services: - mariadb: - image: mariadb:11.1.2-jammy - ports: - - "3306:3306" - environment: - MARIADB_USER: pulp-manager - MARIADB_PASSWORD: pulp-manager - MARIADB_ROOT_PASSWORD: my-root-password - MARIADB_DATABASE: pulp_manager - networks: - - pulp-net - - redis-manager: - image: redis:latest - ports: - - "6379:6379" - networks: - - pulp-net - - pulp-manager-api: - build: ../.. - entrypoint: ["/bin/sh", "-c"] - command: ["/usr/local/bin/pulp-manager -m && /usr/local/bin/pulp-manager -a --no-ssl --dev && /usr/local/bin/pulp-manager -w"] - volumes: - - ../..:/pulp_manager - ports: - - "8080:8000" - environment: - DB_HOSTNAME: mariadb - DB_NAME: pulp_manager - DB_USER: pulp-manager - DB_PASSWORD: pulp-manager - JWT_SECRET: test_secret - PULP_MANAGER_CONFIG_PATH: /pulp_manager/docker/local/config.ini - PULP_SYNC_CONFIG_PATH: /pulp_manager/docker/local/pulp-config.yml - Is_local: true - depends_on: - - mariadb - - redis-manager - networks: - - pulp-net - - pulp-python-worker: - build: ../.. - entrypoint: ["/bin/sh", "-c"] - command: ["/usr/local/bin/pulp-manager -w"] - volumes: - - ../..:/pulp_manager - environment: - DB_HOSTNAME: mariadb - DB_NAME: pulp_manager - DB_USER: pulp-manager - DB_PASSWORD: pulp-manager - JWT_SECRET: test_secret - PULP_MANAGER_CONFIG_PATH: /pulp_manager/docker/local/config.ini - PULP_SYNC_CONFIG_PATH: /pulp_manager/docker/local/pulp-config.yml - Is_local: true - depends_on: - - mariadb - - redis-manager - - pulp-manager-api - networks: - - pulp-net - -networks: - pulp-net: - external: true \ No newline at end of file diff --git a/docker/local/pulp-config.yml b/docker/local/pulp-config.yml deleted file mode 100644 index ab51c5d..0000000 --- a/docker/local/pulp-config.yml +++ /dev/null @@ -1,35 +0,0 @@ -pulp_servers: - # Primary Pulp instance - pulp-web-primary:8080: - credentials: local - repo_config_registration: - schedule: "0,15,30,45 * * * *" - max_runtime: "20m" - repo_groups: - external_repos: - schedule: "00 02 * * *" - max_concurrent_syncs: 2 - max_runtime: "6h" - snapshot_support: - max_concurrent_snapshots: 2 - - # Secondary Pulp instance - syncs from primary - pulp-web-secondary:8080: - credentials: local - repo_groups: - external_repos: - schedule: "00 06 * * *" - max_concurrent_syncs: 2 - max_runtime: "6h" - pulp_master: pulp-web-primary:8080 - -credentials: - local: - username: admin - vault_service_account_mount: service-accounts # optional: Password is loaded from ini file. - -repo_groups: - external_repos: - regex_include: "^ext-" - internal_repos: - regex_include: "^int-" \ No newline at end of file diff --git a/docker/local/pulp-primary.yml b/docker/local/pulp-primary.yml deleted file mode 100644 index b8529f1..0000000 --- a/docker/local/pulp-primary.yml +++ /dev/null @@ -1,215 +0,0 @@ -# Primary Pulp3 instance -version: '3.8' - -services: - postgres-primary: - image: "docker.io/library/postgres:13" - ports: - - "5432:5432" - environment: - POSTGRES_USER: pulp - POSTGRES_PASSWORD: password - POSTGRES_DB: pulp_primary - POSTGRES_INITDB_ARGS: '--auth-host=scram-sha-256' - POSTGRES_HOST_AUTH_METHOD: 'scram-sha-256' - volumes: - - "pg_data_primary:/var/lib/postgresql/data" - restart: always - healthcheck: - test: pg_isready -U pulp -d pulp_primary - interval: 10s - timeout: 5s - retries: 5 - networks: - - pulp-net - - redis-primary: - image: "docker.io/library/redis:latest" - volumes: - - "redis_data_primary:/data" - restart: always - healthcheck: - test: redis-cli ping - interval: 10s - timeout: 5s - retries: 5 - networks: - - pulp-net - - migration-primary: - image: "pulp/pulp-minimal:3.49.1" - depends_on: - postgres-primary: - condition: service_healthy - command: pulpcore-manager migrate --noinput - volumes: - - "../../assets/settings_primary.py:/etc/pulp/settings.py:z" - - "../../assets/certs:/etc/pulp/certs:z" - - "../../assets/keys:/etc/pulp/keys:z" - - "pulp_primary:/var/lib/pulp" - environment: - POSTGRES_HOST: postgres-primary - REDIS_HOST: redis-primary - networks: - - pulp-net - - signing-key-primary: - image: "pulp/pulp-minimal:3.49.1" - command: sh -c "add_signing_service.sh" - depends_on: - postgres-primary: - condition: service_healthy - migration-primary: - condition: service_completed_successfully - environment: - PULP_SIGNING_KEY_FINGERPRINT: '' - POSTGRES_HOST: postgres-primary - REDIS_HOST: redis-primary - volumes: - - "../../assets/settings_primary.py:/etc/pulp/settings.py:z" - - "../../assets/certs:/etc/pulp/certs:z" - - "../../assets/keys:/etc/pulp/keys:z" - - "pulp_primary:/var/lib/pulp" - networks: - - pulp-net - - init-password-primary: - image: "pulp/pulp-minimal:3.49.1" - command: set_init_password.sh - depends_on: - postgres-primary: - condition: service_healthy - environment: - PULP_DEFAULT_ADMIN_PASSWORD: password - POSTGRES_HOST: postgres-primary - REDIS_HOST: redis-primary - volumes: - - "../../assets/settings_primary.py:/etc/pulp/settings.py:z" - - "../../assets/certs:/etc/pulp/certs:z" - - "../../assets/keys:/etc/pulp/keys:z" - - "pulp_primary:/var/lib/pulp" - networks: - - pulp-net - - pulp-web-primary: - image: "pulp/pulp-web:3.49.1" - command: ['nginx', '-g', 'daemon off;'] - depends_on: - pulp-api-primary: - condition: service_healthy - pulp-content-primary: - condition: service_healthy - ports: - - "8000:8080" - hostname: pulp-web-primary - user: root - volumes: - - "../../assets/nginx-conf/nginx-primary.conf:/etc/nginx/nginx.conf:Z" - restart: always - networks: - - pulp-net - - pulp-api-primary: - image: "pulp/pulp-minimal:3.49.1" - deploy: - replicas: 2 - command: ['pulp-api'] - depends_on: - redis-primary: - condition: service_healthy - postgres-primary: - condition: service_healthy - migration-primary: - condition: service_completed_successfully - init-password-primary: - condition: service_completed_successfully - signing-key-primary: - condition: service_completed_successfully - hostname: pulp-api-primary - user: pulp - environment: - PULP_SETTINGS: /etc/pulp/settings.py - PULP_ALLOWED_HOSTS: '["*"]' - POSTGRES_HOST: postgres-primary - REDIS_HOST: redis-primary - volumes: - - "../../assets/settings_primary.py:/etc/pulp/settings.py:z" - - "../../assets/certs:/etc/pulp/certs:z" - - "../../assets/keys:/etc/pulp/keys:z" - - "pulp_primary:/var/lib/pulp" - restart: always - healthcheck: - test: readyz.py /pulp/api/v3/status/ - interval: 10s - timeout: 5s - retries: 5 - networks: - - pulp-net - - pulp-content-primary: - image: "pulp/pulp-minimal:3.49.1" - deploy: - replicas: 2 - command: ['pulp-content'] - depends_on: - redis-primary: - condition: service_healthy - postgres-primary: - condition: service_healthy - migration-primary: - condition: service_completed_successfully - hostname: pulp-content-primary - user: pulp - environment: - POSTGRES_HOST: postgres-primary - REDIS_HOST: redis-primary - volumes: - - "../../assets/settings_primary.py:/etc/pulp/settings.py:z" - - "../../assets/certs:/etc/pulp/certs:z" - - "../../assets/keys:/etc/pulp/keys:z" - - "pulp_primary:/var/lib/pulp" - restart: always - healthcheck: - test: readyz.py /pulp/content/ - interval: 10s - timeout: 5s - retries: 5 - networks: - - pulp-net - - pulp-worker-primary: - image: "pulp/pulp-minimal:3.49.1" - deploy: - replicas: 2 - command: ['pulp-worker'] - depends_on: - redis-primary: - condition: service_healthy - postgres-primary: - condition: service_healthy - migration-primary: - condition: service_completed_successfully - user: pulp - environment: - POSTGRES_HOST: postgres-primary - REDIS_HOST: redis-primary - volumes: - - "../../assets/settings_primary.py:/etc/pulp/settings.py:z" - - "../../assets/certs:/etc/pulp/certs:z" - - "../../assets/keys:/etc/pulp/keys:z" - - "pulp_primary:/var/lib/pulp" - restart: always - networks: - - pulp-net - -volumes: - pulp_primary: - name: pulp_primary_local - pg_data_primary: - name: pg_data_primary_local - redis_data_primary: - name: redis_data_primary_local - -networks: - pulp-net: - external: true \ No newline at end of file diff --git a/docker/local/pulp-secondary.yml b/docker/local/pulp-secondary.yml deleted file mode 100644 index b8cbe0b..0000000 --- a/docker/local/pulp-secondary.yml +++ /dev/null @@ -1,215 +0,0 @@ -# Secondary Pulp3 instance -version: '3.8' - -services: - postgres-secondary: - image: "docker.io/library/postgres:13" - ports: - - "5433:5432" - environment: - POSTGRES_USER: pulp - POSTGRES_PASSWORD: password - POSTGRES_DB: pulp_secondary - POSTGRES_INITDB_ARGS: '--auth-host=scram-sha-256' - POSTGRES_HOST_AUTH_METHOD: 'scram-sha-256' - volumes: - - "pg_data_secondary:/var/lib/postgresql/data" - restart: always - healthcheck: - test: pg_isready -U pulp -d pulp_secondary - interval: 10s - timeout: 5s - retries: 5 - networks: - - pulp-net - - redis-secondary: - image: "docker.io/library/redis:latest" - volumes: - - "redis_data_secondary:/data" - restart: always - healthcheck: - test: redis-cli ping - interval: 10s - timeout: 5s - retries: 5 - networks: - - pulp-net - - migration-secondary: - image: "pulp/pulp-minimal:3.49.1" - depends_on: - postgres-secondary: - condition: service_healthy - command: pulpcore-manager migrate --noinput - volumes: - - "../../assets/settings_secondary.py:/etc/pulp/settings.py:z" - - "../../assets/certs:/etc/pulp/certs:z" - - "../../assets/keys:/etc/pulp/keys:z" - - "pulp_secondary:/var/lib/pulp" - environment: - POSTGRES_HOST: postgres-secondary - REDIS_HOST: redis-secondary - networks: - - pulp-net - - signing-key-secondary: - image: "pulp/pulp-minimal:3.49.1" - command: sh -c "add_signing_service.sh" - depends_on: - postgres-secondary: - condition: service_healthy - migration-secondary: - condition: service_completed_successfully - environment: - PULP_SIGNING_KEY_FINGERPRINT: '' - POSTGRES_HOST: postgres-secondary - REDIS_HOST: redis-secondary - volumes: - - "../../assets/settings_secondary.py:/etc/pulp/settings.py:z" - - "../../assets/certs:/etc/pulp/certs:z" - - "../../assets/keys:/etc/pulp/keys:z" - - "pulp_secondary:/var/lib/pulp" - networks: - - pulp-net - - init-password-secondary: - image: "pulp/pulp-minimal:3.49.1" - command: set_init_password.sh - depends_on: - postgres-secondary: - condition: service_healthy - environment: - PULP_DEFAULT_ADMIN_PASSWORD: password - POSTGRES_HOST: postgres-secondary - REDIS_HOST: redis-secondary - volumes: - - "../../assets/settings_secondary.py:/etc/pulp/settings.py:z" - - "../../assets/certs:/etc/pulp/certs:z" - - "../../assets/keys:/etc/pulp/keys:z" - - "pulp_secondary:/var/lib/pulp" - networks: - - pulp-net - - pulp-web-secondary: - image: "pulp/pulp-web:3.49.1" - command: ['nginx', '-g', 'daemon off;'] - depends_on: - pulp-api-secondary: - condition: service_healthy - pulp-content-secondary: - condition: service_healthy - ports: - - "8001:8080" - hostname: pulp-web-secondary - user: root - volumes: - - "../../assets/nginx-conf/nginx-secondary.conf:/etc/nginx/nginx.conf:Z" - restart: always - networks: - - pulp-net - - pulp-api-secondary: - image: "pulp/pulp-minimal:3.49.1" - deploy: - replicas: 2 - command: ['pulp-api'] - depends_on: - redis-secondary: - condition: service_healthy - postgres-secondary: - condition: service_healthy - migration-secondary: - condition: service_completed_successfully - init-password-secondary: - condition: service_completed_successfully - signing-key-secondary: - condition: service_completed_successfully - hostname: pulp-api-secondary - user: pulp - environment: - PULP_SETTINGS: /etc/pulp/settings.py - PULP_ALLOWED_HOSTS: '["*"]' - POSTGRES_HOST: postgres-secondary - REDIS_HOST: redis-secondary - volumes: - - "../../assets/settings_secondary.py:/etc/pulp/settings.py:z" - - "../../assets/certs:/etc/pulp/certs:z" - - "../../assets/keys:/etc/pulp/keys:z" - - "pulp_secondary:/var/lib/pulp" - restart: always - healthcheck: - test: readyz.py /pulp/api/v3/status/ - interval: 10s - timeout: 5s - retries: 5 - networks: - - pulp-net - - pulp-content-secondary: - image: "pulp/pulp-minimal:3.49.1" - deploy: - replicas: 2 - command: ['pulp-content'] - depends_on: - redis-secondary: - condition: service_healthy - postgres-secondary: - condition: service_healthy - migration-secondary: - condition: service_completed_successfully - hostname: pulp-content-secondary - user: pulp - environment: - POSTGRES_HOST: postgres-secondary - REDIS_HOST: redis-secondary - volumes: - - "../../assets/settings_secondary.py:/etc/pulp/settings.py:z" - - "../../assets/certs:/etc/pulp/certs:z" - - "../../assets/keys:/etc/pulp/keys:z" - - "pulp_secondary:/var/lib/pulp" - restart: always - healthcheck: - test: readyz.py /pulp/content/ - interval: 10s - timeout: 5s - retries: 5 - networks: - - pulp-net - - pulp-worker-secondary: - image: "pulp/pulp-minimal:3.49.1" - deploy: - replicas: 2 - command: ['pulp-worker'] - depends_on: - redis-secondary: - condition: service_healthy - postgres-secondary: - condition: service_healthy - migration-secondary: - condition: service_completed_successfully - user: pulp - environment: - POSTGRES_HOST: postgres-secondary - REDIS_HOST: redis-secondary - volumes: - - "../../assets/settings_secondary.py:/etc/pulp/settings.py:z" - - "../../assets/certs:/etc/pulp/certs:z" - - "../../assets/keys:/etc/pulp/keys:z" - - "pulp_secondary:/var/lib/pulp" - restart: always - networks: - - pulp-net - -volumes: - pulp_secondary: - name: pulp_secondary_local - pg_data_secondary: - name: pg_data_secondary_local - redis_data_secondary: - name: redis_data_secondary_local - -networks: - pulp-net: - external: true \ No newline at end of file diff --git a/docker/simple-cluster.yml b/docker/simple-cluster.yml new file mode 100644 index 0000000..11186d0 --- /dev/null +++ b/docker/simple-cluster.yml @@ -0,0 +1,131 @@ +version: '3.8' + +services: + pulp-primary: + image: pulp/pulp:stable + ports: + - "8000:80" + environment: + - POSTGRES_PASSWORD=password + - PULP_DEFAULT_ADMIN_PASSWORD=password + volumes: + - "./simple/pulp-primary/storage:/var/lib/pulp" + - "./simple/pulp-primary/pgsql:/var/lib/pgsql" + - "./simple/pulp-primary/containers:/var/lib/containers" + - "./simple/settings:/etc/pulp:z" + - "../assets/scripts:/opt/scripts:z" + - "../assets/keys/gpg:/opt/gpg:z" + devices: + - "/dev/fuse" + networks: + - pulp-net + + pulp-secondary: + image: pulp/pulp:stable + ports: + - "8001:80" + environment: + - POSTGRES_PASSWORD=password + - PULP_DEFAULT_ADMIN_PASSWORD=password + volumes: + - "./simple/pulp-secondary/storage:/var/lib/pulp" + - "./simple/pulp-secondary/pgsql:/var/lib/pgsql" + - "./simple/pulp-secondary/containers:/var/lib/containers" + - "./simple/settings:/etc/pulp:z" + - "../assets/scripts:/opt/scripts:z" + - "../assets/keys/gpg:/opt/gpg:z" + devices: + - "/dev/fuse" + networks: + - pulp-net + + # Pulp Manager services + mariadb: + image: mariadb:11.1.2-jammy + ports: + - "3306:3306" + environment: + MARIADB_USER: pulp-manager + MARIADB_PASSWORD: pulp-manager + MARIADB_ROOT_PASSWORD: my-root-password + MARIADB_DATABASE: pulp_manager + networks: + - pulp-net + + redis-manager: + image: redis:latest + ports: + - "6379:6379" + networks: + - pulp-net + + pulp-manager-api: + build: .. + entrypoint: ["/bin/sh", "-c"] + command: ["/usr/local/bin/pulp-manager -m && /usr/local/bin/pulp-manager -a --no-ssl --dev && /usr/local/bin/pulp-manager -w"] + volumes: + - ../..:/pulp_manager + ports: + - "8080:8000" + environment: + DB_HOSTNAME: mariadb + DB_NAME: pulp_manager + DB_USER: pulp-manager + DB_PASSWORD: pulp-manager + JWT_SECRET: test_secret + PULP_MANAGER_CONFIG_PATH: /pulp_manager/docker/simple/config.ini + PULP_SYNC_CONFIG_PATH: /pulp_manager/docker/simple/pulp-config.yml + Is_local: true + depends_on: + - mariadb + - redis-manager + networks: + - pulp-net + + pulp-manager-worker: + build: .. + entrypoint: ["/bin/sh", "-c"] + command: ["/usr/local/bin/pulp-manager -w"] + volumes: + - ../..:/pulp_manager + environment: + DB_HOSTNAME: mariadb + DB_NAME: pulp_manager + DB_USER: pulp-manager + DB_PASSWORD: pulp-manager + JWT_SECRET: test_secret + PULP_MANAGER_CONFIG_PATH: /pulp_manager/docker/simple/config.ini + PULP_SYNC_CONFIG_PATH: /pulp_manager/docker/simple/pulp-config.yml + Is_local: true + depends_on: + - mariadb + - redis-manager + - pulp-manager-api + networks: + - pulp-net + + pulp-manager-scheduler: + build: .. + entrypoint: ["/bin/sh", "-c"] + command: ["/usr/local/bin/pulp-manager -s"] + volumes: + - ../..:/pulp_manager + environment: + DB_HOSTNAME: mariadb + DB_NAME: pulp_manager + DB_USER: pulp-manager + DB_PASSWORD: pulp-manager + JWT_SECRET: test_secret + PULP_MANAGER_CONFIG_PATH: /pulp_manager/docker/simple/config.ini + PULP_SYNC_CONFIG_PATH: /pulp_manager/docker/simple/pulp-config.yml + Is_local: true + depends_on: + - mariadb + - redis-manager + - pulp-manager-api + networks: + - pulp-net + +networks: + pulp-net: + external: true \ No newline at end of file diff --git a/docker/simple/config.ini b/docker/simple/config.ini new file mode 100644 index 0000000..fa02429 --- /dev/null +++ b/docker/simple/config.ini @@ -0,0 +1,34 @@ +[database] +user=pulp-manager +password=pulp-manager +host=mariadb +port=3306 +db_name=pulp_manager + +[auth] +ldap_base_dn=ou=people,dc=pulpproject,dc=com +ldap_group_base_dn=ou=groups,dc=pulpproject,dc=com +ldap_group_class=posixGroup +ldap_user_lookup_attribute=cn +ldap_user_first_name_attribute=givenName +ldap_user_last_name_attribute=sn +ldap_user_email_attribute=mail +ldap_host=ldap://server:389 +ldap_bind_dn=uid=admin,ou=people,dc=pulpproject,dc=com +ldap_bind_password=password + +[redis] +redis_host=redis-manager +redis_port=6379 + +[pulp] +deb_signing_service=pulp_deb +banned_package_regex=bannedexample|another +internal_domains=pulp-primary,pulp-secondary +git_repo_config_dir=repo_config +password=password +internal_package_prefix=int_ +package_name_replacement_pattern= +package_name_replacement_rule= +remote_tls_validation=false +use_https_for_sync=false \ No newline at end of file diff --git a/docker/simple/pulp-config.yml b/docker/simple/pulp-config.yml new file mode 100644 index 0000000..bed715c --- /dev/null +++ b/docker/simple/pulp-config.yml @@ -0,0 +1,23 @@ +pulp_servers: + pulp-primary:80: + credentials: local + repo_config_registration_schedule: "0,15,30,45 * * * *" + + pulp-secondary:80: + credentials: local + repo_groups: + external_repos: + schedule: "*/5 * * * *" # Every 5 minutes + max_concurrent_syncs: 2 + max_runtime: "6h" + pulp_master: pulp-primary:80 + internal_repos: + schedule: "* * * * *" # Every 1 minute + max_concurrent_syncs: 2 + max_runtime: "1h" + pulp_master: pulp-primary:80 + +credentials: + local: + username: admin + password: password \ No newline at end of file diff --git a/docker/simple/pulp-primary/containers/.gitkeep b/docker/simple/pulp-primary/containers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker/simple/pulp-secondary/containers/.gitkeep b/docker/simple/pulp-secondary/containers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker/simple/settings/settings.py b/docker/simple/settings/settings.py new file mode 100644 index 0000000..79e1714 --- /dev/null +++ b/docker/simple/settings/settings.py @@ -0,0 +1,65 @@ +CONTENT_ORIGIN = "http://localhost" + +# Database settings +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'pulp', + 'USER': 'pulp', + 'PASSWORD': 'password', + 'HOST': '127.0.0.1', + 'PORT': '5432', + } +} + +# Cache settings for performance +CACHE_ENABLED = True + +# Logging +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "simple": {"format": "pulp: %(name)s:%(levelname)s: %(message)s"} + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "simple" + } + }, + "loggers": { + "": { + "handlers": ["console"], + "level": "INFO" + } + } +} + +# Task settings +RQ_QUEUES = { + 'default': { + 'HOST': 'localhost', + 'PORT': 6379, + 'DB': 0, + 'DEFAULT_TIMEOUT': 360, + }, +} + +# Enable package signing +PACKAGE_SIGNING_SERVICE = True + +# Token authentication +TOKEN_AUTH_DISABLED = False + +# Content settings +MEDIA_ROOT = "/var/lib/pulp/" + +# Default admin user +PULP_DEFAULT_ADMIN_PASSWORD = "password" +# Signing service configuration +SIGNING_SERVICES = { + 'deb_signing_service': { + 'SCRIPT': '/opt/deb_sign_script.sh', + } +} diff --git a/pulp_manager/app/repositories/table_repository.py b/pulp_manager/app/repositories/table_repository.py index 1488e3f..dfa3333 100644 --- a/pulp_manager/app/repositories/table_repository.py +++ b/pulp_manager/app/repositories/table_repository.py @@ -659,11 +659,18 @@ def bulk_add(self, entities: List): :type entities: list :return: list """ - - result = self.db.scalars( - insert(self.__model__).returning(self.__model__), entities - ) - return result.all() + + # Create model instances from dictionaries + new_entities = [] + for entity_dict in entities: + new_entity = self.__model__(**entity_dict) + self.db.add(new_entity) + new_entities.append(new_entity) + + # Flush to get the IDs assigned + self.db.flush() + + return new_entities def update(self, entity, **kwargs): """Updates existing entity in db but does not commit diff --git a/pulp_manager/app/services/pulp_manager.py b/pulp_manager/app/services/pulp_manager.py index 889d7ab..91b4c90 100644 --- a/pulp_manager/app/services/pulp_manager.py +++ b/pulp_manager/app/services/pulp_manager.py @@ -776,7 +776,7 @@ def create_or_update_repository( :return: PulpServerRepo """ - if "base_url" not in description: + if description is None or "base_url" not in description: raise PulpManagerValueError( f"Could not determine base_url for {name} from description" ) @@ -1023,7 +1023,14 @@ def _generate_feed_from_distribution( :return: str """ - return f"https://{pulp_server_name}/pulp/content/{distribution.base_path}" + protocol = "https" + if "pulp" in CONFIG and "use_https_for_sync" in CONFIG["pulp"]: + use_https = CONFIG["pulp"]["use_https_for_sync"] + if isinstance(use_https, str): + use_https = use_https.lower() == "true" + protocol = "https" if use_https else "http" + + return f"{protocol}://{pulp_server_name}/pulp/content/{distribution.base_path}" def _get_repo_file_list_from_url(self, url: str): """Returns a list of files/directories that exist at the given url. When pulp is hosting @@ -1079,11 +1086,21 @@ def _get_apt_distributions_from_url(self, url: str): # http://pulp3mast1.example.com:24816/pulp/content/ubuntu-20.04-x86_64/focal-backports/ # need to strip 24816 from this. In the future we that logic can be removed if can make # pulp not include this - url = url.replace("http://", "https://") + + # Only convert to HTTPS if configured to do so + use_https_for_sync = True # Default to HTTPS + if "pulp" in CONFIG and "use_https_for_sync" in CONFIG["pulp"]: + use_https = CONFIG["pulp"]["use_https_for_sync"] + if isinstance(use_https, str): + use_https = use_https.lower() == "true" + use_https_for_sync = use_https + + if use_https_for_sync: + url = url.replace("http://", "https://") url = url.replace(":24816", "") if "dists/" not in url: - url = url + "dists/" + url = url.rstrip('/') + "/dists/" # This check is due to _get_apt_distributions_from_url strips off the trailing # / when getting the list of possible distributions, when called recursivley, @@ -1148,7 +1165,7 @@ def _create_or_update_repository_source_pulp_server( if isinstance(source_repo, DebRepository): distributions = self._get_apt_distributions_from_url( - source_distribution.base_url + url ) if len(distributions) == 0: # raise PulpManagerValueError( @@ -1162,9 +1179,14 @@ def _create_or_update_repository_source_pulp_server( log.debug(f"create/update repo source {source_repo.name} URL {url}") + # Create description string in the format expected by create_or_update_repository + description_str = f"base_url: {url}" + if source_repo.description: + description_str += f"\ndescription: {source_repo.description}" + self.create_or_update_repository( name=source_repo.name, - description=source_repo.description, + description=description_str, repo_type=get_repo_type_from_href(source_repo.pulp_href), url=url, distributions=" ".join(distributions) if distributions else None, diff --git a/upload-package.sh b/upload-package.sh new file mode 100755 index 0000000..4d4ef92 --- /dev/null +++ b/upload-package.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +set -e + +echo "Step 1: Getting repository 'internal-demo-packages'..." + +# Try to create repository, if it exists, get the existing one +REPO_CREATE_RESPONSE=$(curl -s -u admin:password -X POST http://localhost:8000/pulp/api/v3/repositories/deb/apt/ \ + -H "Content-Type: application/json" \ + -d '{"name": "internal-demo-packages"}' 2>/dev/null) + +REPO_HREF=$(echo "$REPO_CREATE_RESPONSE" | jq -r '.pulp_href // empty') + +if [ -z "$REPO_HREF" ]; then + echo "Repository already exists, fetching it..." + REPO_LIST_RESPONSE=$(curl -s -u admin:password "http://localhost:8000/pulp/api/v3/repositories/deb/apt/?name=internal-demo-packages") + REPO_HREF=$(echo "$REPO_LIST_RESPONSE" | jq -r '.results[0].pulp_href') +fi + +echo "Repository: $REPO_HREF" + +echo "Step 2: Uploading package content..." +CONTENT_RESPONSE=$(curl -s -u admin:password -X POST http://localhost:8000/pulp/api/v3/content/deb/packages/ \ + -H "Content-Type: multipart/form-data" \ + -F "file=@assets/packages/hello_2.10-2_amd64.deb") + +# Get task href and check status +TASK_HREF=$(echo "$CONTENT_RESPONSE" | jq -r '.task') +echo "Checking upload task status..." + +# Get task result (using full URL to ensure it works) +TASK_RESULT=$(curl -s -u admin:password "http://localhost:8000$TASK_HREF") +TASK_STATUS=$(echo "$TASK_RESULT" | jq -r '.state') + +if [ "$TASK_STATUS" = "failed" ]; then + echo "Content upload task failed!" + echo "Error: $(echo "$TASK_RESULT" | jq -r '.error.description // .error')" + exit 1 +fi + +# Get content href from task result +CONTENT_HREF=$(echo "$TASK_RESULT" | jq -r '.created_resources[0]') +echo "Content created: $CONTENT_HREF" + +echo "Step 3: Adding content to repository..." +MODIFY_RESPONSE=$(curl -s -u admin:password -X POST "http://localhost:8000${REPO_HREF}modify/" \ + -H "Content-Type: application/json" \ + -d "{\"add_content_units\": [\"$CONTENT_HREF\"]}") + +MODIFY_TASK=$(echo "$MODIFY_RESPONSE" | jq -r '.task') +echo "Repository modification task: $MODIFY_TASK" + +echo "✅ Demo package uploaded and added to repository successfully!" \ No newline at end of file From efdfdfb3803213fcd311c9b952d76e98ea2abb10 Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Mon, 15 Sep 2025 15:16:12 -0400 Subject: [PATCH 04/24] updated "upload-package" to "setup-demo.sh", which includes former plus creating needful --- .dockerignore | 8 ++ Dockerfile | 10 +- Makefile | 17 +-- docker/simple-cluster.yml | 10 +- docker/simple/config.ini | 39 +++--- docker/simple/pulp-config.yml | 16 ++- setup-demo.sh | 257 ++++++++++++++++++++++++++++++++++ upload-package.sh | 53 ------- 8 files changed, 315 insertions(+), 95 deletions(-) create mode 100644 .dockerignore create mode 100755 setup-demo.sh delete mode 100755 upload-package.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d7b52f5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +assets/keys/gpg/ +assets/certs/ +.claude/ +.coverage +docker/simple/pulp-primary/storage/ +docker/simple/pulp-primary/pgsql/ +docker/simple/pulp-secondary/storage/ +docker/simple/pulp-secondary/pgsql/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index d31dd7e..4e50084 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,14 +39,8 @@ RUN apt-get update && apt-get install -y netcat-openbsd git make python3-dev lib COPY --from=builder /opt/venv /opt/venv # Copy requirements file COPY --from=builder /pulp_manager/requirements.txt ./ -# Copy application code and other necessary files -COPY alembic.ini pylint.rc pytest.ini wait_db.sh *.yml ./ -ADD alembic ./alembic/. -ADD Makefile . -ADD local_config.ini . -ADD local_pulp_config.yml ./local_pulp_config.yml -ADD pulp-manager.sh . -ADD pulp_manager ./pulp_manager/. +# Copy the entire project +COPY . . # Ensure correct permissions RUN chown -R pulp_manager:pulp_manager /pulp_manager \ diff --git a/Makefile b/Makefile index c935823..d50138e 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ h help: "run-pulp3" "Start Pulp 3 locally with Docker Compose" \ "run-pulp-manager" "Start Pulp Manager with Docker Compose" \ "run-cluster" "Start Pulp3 + Pulp Manger local cluster with Docker Compose" \ - "upload-demo-package" "Upload demo deb package to local cluster pulp3-primary" + "setup-demo" "Setup complete demo environment with repositories and packages" .PHONY : l lint l lint: venv @@ -82,8 +82,9 @@ setup-keys: echo "Container auth keys already exist."; \ fi @mkdir -p assets/keys/gpg - @if [ ! -f assets/keys/gpg/secring.gpg ]; then \ + @if [ ! -f assets/keys/gpg/public.key ] || [ ! -s assets/keys/gpg/public.key ]; then \ echo "Generating GPG signing keys..."; \ + rm -rf assets/keys/gpg/*; \ chmod 700 assets/keys/gpg; \ echo "Key-Type: RSA" > /tmp/gpg-batch-config; \ echo "Key-Length: 2048" >> /tmp/gpg-batch-config; \ @@ -92,15 +93,15 @@ setup-keys: echo "Expire-Date: 0" >> /tmp/gpg-batch-config; \ echo "%no-protection" >> /tmp/gpg-batch-config; \ echo "%commit" >> /tmp/gpg-batch-config; \ - GNUPGHOME=assets/keys/gpg gpg --batch --gen-key /tmp/gpg-batch-config; \ - GNUPGHOME=assets/keys/gpg gpg --armor --export > assets/keys/gpg/public.key; \ + GNUPGHOME=assets/keys/gpg gpg --batch --no-default-keyring --keyring assets/keys/gpg/pubring.kbx --gen-key /tmp/gpg-batch-config; \ + GNUPGHOME=assets/keys/gpg gpg --no-default-keyring --keyring assets/keys/gpg/pubring.kbx --armor --export > assets/keys/gpg/public.key; \ rm /tmp/gpg-batch-config; \ echo "GPG signing keys created."; \ else \ echo "GPG signing keys already exist."; \ fi -.PHONY : upload-demo-package -upload-demo-package: - @echo "Uploading demo package to pulp3-primary..." - @./upload-package.sh +.PHONY : setup-demo +setup-demo: + @echo "Setting up complete demo environment..." + @./setup-demo.sh diff --git a/docker/simple-cluster.yml b/docker/simple-cluster.yml index 11186d0..e51b97a 100644 --- a/docker/simple-cluster.yml +++ b/docker/simple-cluster.yml @@ -61,10 +61,8 @@ services: pulp-manager-api: build: .. - entrypoint: ["/bin/sh", "-c"] - command: ["/usr/local/bin/pulp-manager -m && /usr/local/bin/pulp-manager -a --no-ssl --dev && /usr/local/bin/pulp-manager -w"] - volumes: - - ../..:/pulp_manager + entrypoint: ["/bin/sh", "-c"] + command: ["/usr/local/bin/pulp-manager -m && /usr/local/bin/pulp-manager -a --no-ssl --dev"] ports: - "8080:8000" environment: @@ -86,8 +84,6 @@ services: build: .. entrypoint: ["/bin/sh", "-c"] command: ["/usr/local/bin/pulp-manager -w"] - volumes: - - ../..:/pulp_manager environment: DB_HOSTNAME: mariadb DB_NAME: pulp_manager @@ -108,8 +104,6 @@ services: build: .. entrypoint: ["/bin/sh", "-c"] command: ["/usr/local/bin/pulp-manager -s"] - volumes: - - ../..:/pulp_manager environment: DB_HOSTNAME: mariadb DB_NAME: pulp_manager diff --git a/docker/simple/config.ini b/docker/simple/config.ini index fa02429..582c665 100644 --- a/docker/simple/config.ini +++ b/docker/simple/config.ini @@ -6,23 +6,17 @@ port=3306 db_name=pulp_manager [auth] -ldap_base_dn=ou=people,dc=pulpproject,dc=com -ldap_group_base_dn=ou=groups,dc=pulpproject,dc=com -ldap_group_class=posixGroup -ldap_user_lookup_attribute=cn -ldap_user_first_name_attribute=givenName -ldap_user_last_name_attribute=sn -ldap_user_email_attribute=mail -ldap_host=ldap://server:389 -ldap_bind_dn=uid=admin,ou=people,dc=pulpproject,dc=com -ldap_bind_password=password - -[redis] -redis_host=redis-manager -redis_port=6379 +method=ldap +use_ssl=false +ldap_servers=server:389 +base_dn=ou=people,dc=pulpproject,dc=com +default_domain=pulpproject.com +jwt_algorithm=HS256 +jwt_token_lifetime_mins=480 +admin_group=pulpmaster-rw [pulp] -deb_signing_service=pulp_deb +# deb_signing_service=pulp_deb banned_package_regex=bannedexample|another internal_domains=pulp-primary,pulp-secondary git_repo_config_dir=repo_config @@ -31,4 +25,17 @@ internal_package_prefix=int_ package_name_replacement_pattern= package_name_replacement_rule= remote_tls_validation=false -use_https_for_sync=false \ No newline at end of file + +[redis] +host=redis-manager +port=6379 +db=0 +max_page_size=24 + +[remotes] +sock_connect_timeout=120.0 +sock_read_timeout=600.0 + +[paging] +default_page_size=50 +max_page_size=20000 \ No newline at end of file diff --git a/docker/simple/pulp-config.yml b/docker/simple/pulp-config.yml index bed715c..4bc5438 100644 --- a/docker/simple/pulp-config.yml +++ b/docker/simple/pulp-config.yml @@ -1,7 +1,11 @@ pulp_servers: pulp-primary:80: credentials: local - repo_config_registration_schedule: "0,15,30,45 * * * *" + repo_groups: + default: + schedule: "0 * * * *" # Every hour + max_concurrent_syncs: 1 + max_runtime: "1h" pulp-secondary:80: credentials: local @@ -20,4 +24,12 @@ pulp_servers: credentials: local: username: admin - password: password \ No newline at end of file + vault_service_account_mount: local-auth + +repo_groups: + default: + regex_include: ".*" # Match all repositories + external_repos: + regex_include: "^ext" # Match repositories starting with "ext" + internal_repos: + regex_include: "^int" # Match repositories starting with "int" \ No newline at end of file diff --git a/setup-demo.sh b/setup-demo.sh new file mode 100755 index 0000000..13441c1 --- /dev/null +++ b/setup-demo.sh @@ -0,0 +1,257 @@ +#!/bin/bash + +set -e + +PULP_PRIMARY="http://localhost:8000" +PULP_SECONDARY="http://localhost:8001" +PULP_USER="admin" +PULP_PASS="password" + +echo "🚀 Setting up Pulp Demo Environment" +echo "==================================" + +# Function to check if a resource exists by name +check_exists() { + local url="$1" + local name="$2" + local response=$(curl -s -u $PULP_USER:$PULP_PASS "$url?name=$name") + local count=$(echo "$response" | jq -r '.count // 0') + [ "$count" -gt 0 ] +} + +# Function to wait for task completion +wait_for_task() { + local task_href="$1" + local server="$2" + echo " Waiting for task to complete..." + + while true; do + local task_result=$(curl -s -u $PULP_USER:$PULP_PASS "$server$task_href") + local state=$(echo "$task_result" | jq -r '.state') + + case "$state" in + "completed") + echo " ✅ Task completed successfully" + return 0 + ;; + "failed") + echo " ❌ Task failed!" + echo " Error: $(echo "$task_result" | jq -r '.error.description // .error')" + return 1 + ;; + "running"|"waiting") + echo " ⏳ Task $state, waiting..." + sleep 2 + ;; + *) + echo " ⏳ Task in state: $state, waiting..." + sleep 2 + ;; + esac + done +} + +echo "" +echo "Step 1: Creating External Repository (ext-small-repo)" +echo "====================================================" + +# Create external repository +if check_exists "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/" "ext-small-repo"; then + echo " ✅ Repository 'ext-small-repo' already exists" + EXT_REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/?name=ext-small-repo") + EXT_REPO_HREF=$(echo "$EXT_REPO_RESPONSE" | jq -r '.results[0].pulp_href') +else + echo " 📦 Creating repository 'ext-small-repo'..." + EXT_REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/" \ + -H "Content-Type: application/json" \ + -d '{"name": "ext-small-repo", "description": "External small Debian testing repository"}') + EXT_REPO_HREF=$(echo "$EXT_REPO_RESPONSE" | jq -r '.pulp_href') + echo " ✅ Repository created: $EXT_REPO_HREF" +fi + +# Create remote for Debian testing with limited components +if check_exists "$PULP_PRIMARY/pulp/api/v3/remotes/deb/apt/" "debian-testing-remote"; then + echo " ✅ Remote 'debian-testing-remote' already exists" + REMOTE_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY/pulp/api/v3/remotes/deb/apt/?name=debian-testing-remote") + REMOTE_HREF=$(echo "$REMOTE_RESPONSE" | jq -r '.results[0].pulp_href') +else + echo " 🌐 Creating remote for Debian testing (limited components)..." + REMOTE_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/remotes/deb/apt/" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "debian-testing-remote", + "url": "http://deb.debian.org/debian/", + "distributions": "testing", + "components": "main", + "architectures": "amd64", + "sync_sources": false, + "sync_udebs": false, + "sync_installer": false + }') + REMOTE_HREF=$(echo "$REMOTE_RESPONSE" | jq -r '.pulp_href') + echo " ✅ Remote created: $REMOTE_HREF" +fi + +# Associate remote with repository +echo " 🔗 Associating remote with repository..." +REPO_UPDATE_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X PATCH "$PULP_PRIMARY$EXT_REPO_HREF" \ + -H "Content-Type: application/json" \ + -d "{\"remote\": \"$REMOTE_HREF\"}") + +if echo "$REPO_UPDATE_RESPONSE" | jq -e '.task' > /dev/null; then + TASK_HREF=$(echo "$REPO_UPDATE_RESPONSE" | jq -r '.task') + wait_for_task "$TASK_HREF" "$PULP_PRIMARY" +fi + +echo "" +echo "Step 2: Creating Internal Repository (int-demo-packages)" +echo "=======================================================" + +# Create internal repository +if check_exists "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/" "int-demo-packages"; then + echo " ✅ Repository 'int-demo-packages' already exists" + INT_REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/?name=int-demo-packages") + INT_REPO_HREF=$(echo "$INT_REPO_RESPONSE" | jq -r '.results[0].pulp_href') +else + echo " 📦 Creating repository 'int-demo-packages'..." + INT_REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/" \ + -H "Content-Type: application/json" \ + -d '{"name": "int-demo-packages", "description": "Internal demo repository"}') + INT_REPO_HREF=$(echo "$INT_REPO_RESPONSE" | jq -r '.pulp_href') + echo " ✅ Repository created: $INT_REPO_HREF" +fi + +echo "" +echo "Step 3: Uploading Demo Package to Internal Repository" +echo "=====================================================" + +# Check if package already exists in repository +INT_REPO_CONTENT=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY${INT_REPO_HREF}versions/1/content/") +HELLO_PKG_EXISTS=$(echo "$INT_REPO_CONTENT" | jq -r '.results[] | select(.summary and (.summary | contains("hello"))) | .pulp_href' | head -n1) + +if [ -n "$HELLO_PKG_EXISTS" ] && [ "$HELLO_PKG_EXISTS" != "null" ]; then + echo " ✅ Demo package already exists in repository" +else + echo " 📤 Uploading demo package..." + CONTENT_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/content/deb/packages/" \ + -H "Content-Type: multipart/form-data" \ + -F "file=@assets/packages/hello_2.10-2_amd64.deb") + + UPLOAD_TASK_HREF=$(echo "$CONTENT_RESPONSE" | jq -r '.task') + wait_for_task "$UPLOAD_TASK_HREF" "$PULP_PRIMARY" + + # Get content href from completed task + TASK_RESULT=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY$UPLOAD_TASK_HREF") + CONTENT_HREF=$(echo "$TASK_RESULT" | jq -r '.created_resources[0]') + echo " 📦 Content created: $CONTENT_HREF" + + # Add content to repository + echo " ➕ Adding content to repository..." + MODIFY_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY${INT_REPO_HREF}modify/" \ + -H "Content-Type: application/json" \ + -d "{\"add_content_units\": [\"$CONTENT_HREF\"]}") + + MODIFY_TASK_HREF=$(echo "$MODIFY_RESPONSE" | jq -r '.task') + wait_for_task "$MODIFY_TASK_HREF" "$PULP_PRIMARY" +fi + +echo "" +echo "Step 4: Creating Publications" +echo "=============================" + +# Create publication for external repository +echo " 📰 Creating publication for ext-small-repo..." +EXT_PUB_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/publications/deb/apt/" \ + -H "Content-Type: application/json" \ + -d "{\"repository\": \"$EXT_REPO_HREF\"}") + +if echo "$EXT_PUB_RESPONSE" | jq -e '.task' > /dev/null; then + EXT_PUB_TASK=$(echo "$EXT_PUB_RESPONSE" | jq -r '.task') + wait_for_task "$EXT_PUB_TASK" "$PULP_PRIMARY" + + # Get publication href + PUB_TASK_RESULT=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY$EXT_PUB_TASK") + EXT_PUB_HREF=$(echo "$PUB_TASK_RESULT" | jq -r '.created_resources[0]') + echo " ✅ External publication created: $EXT_PUB_HREF" +else + EXT_PUB_HREF=$(echo "$EXT_PUB_RESPONSE" | jq -r '.pulp_href') + echo " ✅ External publication exists: $EXT_PUB_HREF" +fi + +# Create publication for internal repository +echo " 📰 Creating publication for int-demo-packages..." +INT_PUB_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/publications/deb/apt/" \ + -H "Content-Type: application/json" \ + -d "{\"repository\": \"$INT_REPO_HREF\"}") + +if echo "$INT_PUB_RESPONSE" | jq -e '.task' > /dev/null; then + INT_PUB_TASK=$(echo "$INT_PUB_RESPONSE" | jq -r '.task') + wait_for_task "$INT_PUB_TASK" "$PULP_PRIMARY" + + # Get publication href + PUB_TASK_RESULT=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY$INT_PUB_TASK") + INT_PUB_HREF=$(echo "$PUB_TASK_RESULT" | jq -r '.created_resources[0]') + echo " ✅ Internal publication created: $INT_PUB_HREF" +else + INT_PUB_HREF=$(echo "$INT_PUB_RESPONSE" | jq -r '.pulp_href') + echo " ✅ Internal publication exists: $INT_PUB_HREF" +fi + +echo "" +echo "Step 5: Creating Distributions" +echo "==============================" + +# Create distribution for external repository +if check_exists "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/" "ext-small-repo"; then + echo " ✅ Distribution 'ext-small-repo' already exists" +else + echo " 🌐 Creating distribution for ext-small-repo..." + EXT_DIST_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/" \ + -H "Content-Type: application/json" \ + -d "{ + \"name\": \"ext-small-repo\", + \"base_path\": \"ext-small-repo\", + \"publication\": \"$EXT_PUB_HREF\" + }") + + if echo "$EXT_DIST_RESPONSE" | jq -e '.task' > /dev/null; then + EXT_DIST_TASK=$(echo "$EXT_DIST_RESPONSE" | jq -r '.task') + wait_for_task "$EXT_DIST_TASK" "$PULP_PRIMARY" + fi + echo " ✅ External distribution created" +fi + +# Create distribution for internal repository +if check_exists "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/" "int-demo-packages"; then + echo " ✅ Distribution 'int-demo-packages' already exists" +else + echo " 🌐 Creating distribution for int-demo-packages..." + INT_DIST_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/" \ + -H "Content-Type: application/json" \ + -d "{ + \"name\": \"int-demo-packages\", + \"base_path\": \"int-demo-packages\", + \"publication\": \"$INT_PUB_HREF\" + }") + + if echo "$INT_DIST_RESPONSE" | jq -e '.task' > /dev/null; then + INT_DIST_TASK=$(echo "$INT_DIST_RESPONSE" | jq -r '.task') + wait_for_task "$INT_DIST_TASK" "$PULP_PRIMARY" + fi + echo " ✅ Internal distribution created" +fi + +echo "" +echo "🎉 Demo Setup Complete!" +echo "======================" +echo "" +echo "Available repositories:" +echo " 📦 ext-small-repo (external): $PULP_PRIMARY/pulp/content/ext-small-repo/" +echo " 📦 int-demo-packages (internal): $PULP_PRIMARY/pulp/content/int-demo-packages/" +echo "" +echo "Next steps:" +echo " 1. Sync external repository: curl -u admin:password -X POST '$PULP_PRIMARY${EXT_REPO_HREF}sync/' -H 'Content-Type: application/json' -d '{\"remote\": \"$REMOTE_HREF\"}'" +echo " 2. Check sync status via Pulp Manager API" +echo " 3. Configure secondary server to sync from primary" +echo "" +echo "View repositories: curl -u admin:password '$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/' | jq '.results[] | {name, pulp_href}'" \ No newline at end of file diff --git a/upload-package.sh b/upload-package.sh deleted file mode 100755 index 4d4ef92..0000000 --- a/upload-package.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash - -set -e - -echo "Step 1: Getting repository 'internal-demo-packages'..." - -# Try to create repository, if it exists, get the existing one -REPO_CREATE_RESPONSE=$(curl -s -u admin:password -X POST http://localhost:8000/pulp/api/v3/repositories/deb/apt/ \ - -H "Content-Type: application/json" \ - -d '{"name": "internal-demo-packages"}' 2>/dev/null) - -REPO_HREF=$(echo "$REPO_CREATE_RESPONSE" | jq -r '.pulp_href // empty') - -if [ -z "$REPO_HREF" ]; then - echo "Repository already exists, fetching it..." - REPO_LIST_RESPONSE=$(curl -s -u admin:password "http://localhost:8000/pulp/api/v3/repositories/deb/apt/?name=internal-demo-packages") - REPO_HREF=$(echo "$REPO_LIST_RESPONSE" | jq -r '.results[0].pulp_href') -fi - -echo "Repository: $REPO_HREF" - -echo "Step 2: Uploading package content..." -CONTENT_RESPONSE=$(curl -s -u admin:password -X POST http://localhost:8000/pulp/api/v3/content/deb/packages/ \ - -H "Content-Type: multipart/form-data" \ - -F "file=@assets/packages/hello_2.10-2_amd64.deb") - -# Get task href and check status -TASK_HREF=$(echo "$CONTENT_RESPONSE" | jq -r '.task') -echo "Checking upload task status..." - -# Get task result (using full URL to ensure it works) -TASK_RESULT=$(curl -s -u admin:password "http://localhost:8000$TASK_HREF") -TASK_STATUS=$(echo "$TASK_RESULT" | jq -r '.state') - -if [ "$TASK_STATUS" = "failed" ]; then - echo "Content upload task failed!" - echo "Error: $(echo "$TASK_RESULT" | jq -r '.error.description // .error')" - exit 1 -fi - -# Get content href from task result -CONTENT_HREF=$(echo "$TASK_RESULT" | jq -r '.created_resources[0]') -echo "Content created: $CONTENT_HREF" - -echo "Step 3: Adding content to repository..." -MODIFY_RESPONSE=$(curl -s -u admin:password -X POST "http://localhost:8000${REPO_HREF}modify/" \ - -H "Content-Type: application/json" \ - -d "{\"add_content_units\": [\"$CONTENT_HREF\"]}") - -MODIFY_TASK=$(echo "$MODIFY_RESPONSE" | jq -r '.task') -echo "Repository modification task: $MODIFY_TASK" - -echo "✅ Demo package uploaded and added to repository successfully!" \ No newline at end of file From 4e186fc03977a8923387975542b9c119082f06db Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Thu, 25 Sep 2025 12:55:14 -0400 Subject: [PATCH 05/24] remove unused assets Signed-off-by: Geoff Wilson --- assets/bin/nginx.sh | 32 ---- assets/nginx-conf/nginx-primary.conf | 137 ------------- assets/nginx-conf/nginx-secondary.conf | 137 ------------- assets/nginx-conf/nginx.conf.template | 89 --------- assets/nginx/nginx.conf.template | 89 --------- assets/packages/nano_7.2-1_amd64.deb | 0 assets/settings.py | 19 -- assets/settings_primary.py | 19 -- assets/settings_secondary.py | 19 -- docker/simple-cluster.yml | 20 ++ local_config.ini | 43 ----- local_pulp_config.yml | 23 --- pulp_manager/app/routers/v1/pulp_servers.py | 3 - setup-demo.sh | 201 +++++++++++++------- 14 files changed, 152 insertions(+), 679 deletions(-) delete mode 100755 assets/bin/nginx.sh delete mode 100644 assets/nginx-conf/nginx-primary.conf delete mode 100644 assets/nginx-conf/nginx-secondary.conf delete mode 100644 assets/nginx-conf/nginx.conf.template delete mode 100644 assets/nginx/nginx.conf.template delete mode 100644 assets/packages/nano_7.2-1_amd64.deb delete mode 100644 assets/settings.py delete mode 100644 assets/settings_primary.py delete mode 100644 assets/settings_secondary.py delete mode 100644 local_config.ini delete mode 100644 local_pulp_config.yml diff --git a/assets/bin/nginx.sh b/assets/bin/nginx.sh deleted file mode 100755 index d9bfdd1..0000000 --- a/assets/bin/nginx.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -# This logic enables us to have multiple servers, and check to see -# if they are scaled every 10 seconds. -# https://serverfault.com/a/821625/189494 -# https://www.nginx.com/blog/dns-service-discovery-nginx-plus#domain-name-variable - -set -e - -if [ "$container" == "podman" ]; then - # the nameserver list under podman is unreliable. - # It will look like "10.89.1.1 192.168.1.1 192.168.1.1", but only the 1st IP works. - # This doesn't mess up `nslookup`, but it messes up `getent hosts` and nginx. - export NAMESERVER=`cat /etc/resolv.conf | grep "nameserver" | awk '{print $2}' | head -n1` -else - export NAMESERVER=`cat /etc/resolv.conf | grep "nameserver" | awk '{print $2}' | tr '\n' ' '` -fi - -echo "Nameserver is: $NAMESERVER" - -echo "Generating nginx config" -envsubst '$NAMESERVER' < ../nginx/nginx.conf.template > /etc/nginx/nginx.conf - -# We cannot use upstream server groups with a DNS resolver without nginx plus -# So we modifying the files to use the variables rather than the upstream server groups -for file in /etc/nginx/conf.d/*.conf ; do - echo "Modifying $file" - sed -i 's/pulp-api/$pulp_api:24817/' $file - sed -i 's/pulp-content/$pulp_content:24816/' $file -done - -echo "Starting nginx" -exec nginx -g "daemon off;" \ No newline at end of file diff --git a/assets/nginx-conf/nginx-primary.conf b/assets/nginx-conf/nginx-primary.conf deleted file mode 100644 index de9f7e4..0000000 --- a/assets/nginx-conf/nginx-primary.conf +++ /dev/null @@ -1,137 +0,0 @@ -error_log /dev/stdout info; -worker_processes 1; -events { - worker_connections 1024; # increase if you have lots of clients - accept_mutex off; # set to 'on' if nginx worker_processes > 1 -} - -http { - access_log /dev/stdout; - include mime.types; - # fallback in case we can't determine a type - default_type application/octet-stream; - sendfile on; - - # If left at the default of 1024, nginx emits a warning about being unable - # to build optimal hash types. - types_hash_max_size 4096; - - server { - # This logic enables us to have multiple servers, and check to see - # if they are scaled every 10 seconds. - # https://www.nginx.com/blog/dns-service-discovery-nginx-plus#domain-name-variable - # https://serverfault.com/a/821625/189494 - resolver 127.0.0.11 valid=10s; - set $pulp_api pulp-api-primary; - set $pulp_content pulp-content-primary; - - # Gunicorn docs suggest the use of the "deferred" directive on Linux. - listen 8080 default_server deferred; - listen [::]:8080 default_server deferred; - - # If you have a domain name, this is where to add it - server_name $hostname; - - # The default client_max_body_size is 1m. Clients uploading - # files larger than this will need to chunk said files. - client_max_body_size 10m; - - # Gunicorn docs suggest this value. - keepalive_timeout 5; - - # static files that can change dynamically, or are needed for TLS - # purposes are served through the webserver. - root /opt/app-root/src; - - location /pulp/content/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_content:24816; - } - - location /pulp/api/v3/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - location /auth/login/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - # Additional Pulp service endpoints - location /pypi/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - location /v2/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - client_max_body_size 0; - } - - location /extensions/v2/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - location /pulp/container/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_redirect off; - proxy_pass http://$pulp_content:24816; - } - - location /token/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - location /pulp_ansible/galaxy/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - location / { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - # static files are served through whitenoise - http://whitenoise.evans.io/en/stable/ - } - } -} \ No newline at end of file diff --git a/assets/nginx-conf/nginx-secondary.conf b/assets/nginx-conf/nginx-secondary.conf deleted file mode 100644 index c94d26a..0000000 --- a/assets/nginx-conf/nginx-secondary.conf +++ /dev/null @@ -1,137 +0,0 @@ -error_log /dev/stdout info; -worker_processes 1; -events { - worker_connections 1024; # increase if you have lots of clients - accept_mutex off; # set to 'on' if nginx worker_processes > 1 -} - -http { - access_log /dev/stdout; - include mime.types; - # fallback in case we can't determine a type - default_type application/octet-stream; - sendfile on; - - # If left at the default of 1024, nginx emits a warning about being unable - # to build optimal hash types. - types_hash_max_size 4096; - - server { - # This logic enables us to have multiple servers, and check to see - # if they are scaled every 10 seconds. - # https://www.nginx.com/blog/dns-service-discovery-nginx-plus#domain-name-variable - # https://serverfault.com/a/821625/189494 - resolver 127.0.0.11 valid=10s; - set $pulp_api pulp-api-secondary; - set $pulp_content pulp-content-secondary; - - # Gunicorn docs suggest the use of the "deferred" directive on Linux. - listen 8080 default_server deferred; - listen [::]:8080 default_server deferred; - - # If you have a domain name, this is where to add it - server_name $hostname; - - # The default client_max_body_size is 1m. Clients uploading - # files larger than this will need to chunk said files. - client_max_body_size 10m; - - # Gunicorn docs suggest this value. - keepalive_timeout 5; - - # static files that can change dynamically, or are needed for TLS - # purposes are served through the webserver. - root /opt/app-root/src; - - location /pulp/content/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_content:24816; - } - - location /pulp/api/v3/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - location /auth/login/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - # Additional Pulp service endpoints - location /pypi/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - location /v2/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - client_max_body_size 0; - } - - location /extensions/v2/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - location /pulp/container/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_redirect off; - proxy_pass http://$pulp_content:24816; - } - - location /token/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - location /pulp_ansible/galaxy/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - location / { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - # static files are served through whitenoise - http://whitenoise.evans.io/en/stable/ - } - } -} \ No newline at end of file diff --git a/assets/nginx-conf/nginx.conf.template b/assets/nginx-conf/nginx.conf.template deleted file mode 100644 index 3707d09..0000000 --- a/assets/nginx-conf/nginx.conf.template +++ /dev/null @@ -1,89 +0,0 @@ -error_log /dev/stdout info; -worker_processes 1; -events { - worker_connections 1024; # increase if you have lots of clients - accept_mutex off; # set to 'on' if nginx worker_processes > 1 -} - -http { - access_log /dev/stdout; - include mime.types; - # fallback in case we can't determine a type - default_type application/octet-stream; - sendfile on; - - # If left at the default of 1024, nginx emits a warning about being unable - # to build optimal hash types. - types_hash_max_size 4096; - - server { - # This logic enables us to have multiple servers, and check to see - # if they are scaled every 10 seconds. - # https://www.nginx.com/blog/dns-service-discovery-nginx-plus#domain-name-variable - # https://serverfault.com/a/821625/189494 - resolver $NAMESERVER valid=10s; - set $pulp_api pulp_api; - set $pulp_content pulp_content; - - # Gunicorn docs suggest the use of the "deferred" directive on Linux. - listen 8080 default_server deferred; - listen [::]:8080 default_server deferred; - - # If you have a domain name, this is where to add it - server_name $hostname; - - # The default client_max_body_size is 1m. Clients uploading - # files larger than this will need to chunk said files. - client_max_body_size 10m; - - # Gunicorn docs suggest this value. - keepalive_timeout 5; - - # static files that can change dynamically, or are needed for TLS - # purposes are served through the webserver. - root /opt/app-root/src; - - location /pulp/content/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_content:24816; - } - - location /pulp/api/v3/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - location /auth/login/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - include /opt/app-root/etc/nginx.default.d/*.conf; - - location / { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - # static files are served through whitenoise - http://whitenoise.evans.io/en/stable/ - } - } -} \ No newline at end of file diff --git a/assets/nginx/nginx.conf.template b/assets/nginx/nginx.conf.template deleted file mode 100644 index 3707d09..0000000 --- a/assets/nginx/nginx.conf.template +++ /dev/null @@ -1,89 +0,0 @@ -error_log /dev/stdout info; -worker_processes 1; -events { - worker_connections 1024; # increase if you have lots of clients - accept_mutex off; # set to 'on' if nginx worker_processes > 1 -} - -http { - access_log /dev/stdout; - include mime.types; - # fallback in case we can't determine a type - default_type application/octet-stream; - sendfile on; - - # If left at the default of 1024, nginx emits a warning about being unable - # to build optimal hash types. - types_hash_max_size 4096; - - server { - # This logic enables us to have multiple servers, and check to see - # if they are scaled every 10 seconds. - # https://www.nginx.com/blog/dns-service-discovery-nginx-plus#domain-name-variable - # https://serverfault.com/a/821625/189494 - resolver $NAMESERVER valid=10s; - set $pulp_api pulp_api; - set $pulp_content pulp_content; - - # Gunicorn docs suggest the use of the "deferred" directive on Linux. - listen 8080 default_server deferred; - listen [::]:8080 default_server deferred; - - # If you have a domain name, this is where to add it - server_name $hostname; - - # The default client_max_body_size is 1m. Clients uploading - # files larger than this will need to chunk said files. - client_max_body_size 10m; - - # Gunicorn docs suggest this value. - keepalive_timeout 5; - - # static files that can change dynamically, or are needed for TLS - # purposes are served through the webserver. - root /opt/app-root/src; - - location /pulp/content/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_content:24816; - } - - location /pulp/api/v3/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - location /auth/login/ { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - } - - include /opt/app-root/etc/nginx.default.d/*.conf; - - location / { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - # we don't want nginx trying to do something clever with - # redirects, we set the Host: header above already. - proxy_redirect off; - proxy_pass http://$pulp_api:24817; - # static files are served through whitenoise - http://whitenoise.evans.io/en/stable/ - } - } -} \ No newline at end of file diff --git a/assets/packages/nano_7.2-1_amd64.deb b/assets/packages/nano_7.2-1_amd64.deb deleted file mode 100644 index e69de29..0000000 diff --git a/assets/settings.py b/assets/settings.py deleted file mode 100644 index 04b5590..0000000 --- a/assets/settings.py +++ /dev/null @@ -1,19 +0,0 @@ -SECRET_KEY = "aabbcc" -CONTENT_ORIGIN = "http://pulp_content:24816" -DATABASES = {"default": {"HOST": "postgres", "ENGINE": "django.db.backends.postgresql", "NAME": "pulp", "USER": "pulp", "PASSWORD": "password", "PORT": "5432", "CONN_MAX_AGE": 0, "OPTIONS": {"sslmode": "prefer"}}} -CACHE_ENABLED = True -REDIS_HOST = "redis" -REDIS_PORT = 6379 -REDIS_PASSWORD = "" -ANSIBLE_API_HOSTNAME = "http://pulp_api:24817" -ANSIBLE_CONTENT_HOSTNAME = "http://pulp_content:24816/pulp/content" -ALLOWED_IMPORT_PATHS = ["/tmp"] -ALLOWED_EXPORT_PATHS = ["/tmp"] -TOKEN_SERVER = "http://pulp_api:24817/token/" -TOKEN_AUTH_DISABLED = False -TOKEN_SIGNATURE_ALGORITHM = "ES256" -PUBLIC_KEY_PATH = "/etc/pulp/keys/container_auth_public_key.pem" -PRIVATE_KEY_PATH = "/etc/pulp/keys/container_auth_private_key.pem" -ANALYTICS = False -STATIC_ROOT = "/var/lib/operator/static/" -ALLOWED_HOSTS = ['pulp-web:8080', 'localhost', '127.0.0.1', '[::1]'] \ No newline at end of file diff --git a/assets/settings_primary.py b/assets/settings_primary.py deleted file mode 100644 index a88eef4..0000000 --- a/assets/settings_primary.py +++ /dev/null @@ -1,19 +0,0 @@ -SECRET_KEY = "aabbcc" -CONTENT_ORIGIN = "http://pulp-content-primary:24816" -DATABASES = {"default": {"HOST": "postgres-primary", "ENGINE": "django.db.backends.postgresql", "NAME": "pulp_primary", "USER": "pulp", "PASSWORD": "password", "PORT": "5432", "CONN_MAX_AGE": 0, "OPTIONS": {"sslmode": "prefer"}}} -CACHE_ENABLED = True -REDIS_HOST = "redis-primary" -REDIS_PORT = 6379 -REDIS_PASSWORD = "" -ANSIBLE_API_HOSTNAME = "http://pulp-api-primary:24817" -ANSIBLE_CONTENT_HOSTNAME = "http://pulp-content-primary:24816/pulp/content" -ALLOWED_IMPORT_PATHS = ["/tmp"] -ALLOWED_EXPORT_PATHS = ["/tmp"] -TOKEN_SERVER = "http://pulp-api-primary:24817/token/" -TOKEN_AUTH_DISABLED = False -TOKEN_SIGNATURE_ALGORITHM = "ES256" -PUBLIC_KEY_PATH = "/etc/pulp/keys/container_auth_public_key.pem" -PRIVATE_KEY_PATH = "/etc/pulp/keys/container_auth_private_key.pem" -ANALYTICS = False -STATIC_ROOT = "/var/lib/operator/static/" -ALLOWED_HOSTS = ['pulp-web-primary:8080', 'localhost', '127.0.0.1', '[::1]'] \ No newline at end of file diff --git a/assets/settings_secondary.py b/assets/settings_secondary.py deleted file mode 100644 index 908b9c7..0000000 --- a/assets/settings_secondary.py +++ /dev/null @@ -1,19 +0,0 @@ -SECRET_KEY = "aabbcc" -CONTENT_ORIGIN = "http://pulp-content-secondary:24816" -DATABASES = {"default": {"HOST": "postgres-secondary", "ENGINE": "django.db.backends.postgresql", "NAME": "pulp_secondary", "USER": "pulp", "PASSWORD": "password", "PORT": "5432", "CONN_MAX_AGE": 0, "OPTIONS": {"sslmode": "prefer"}}} -CACHE_ENABLED = True -REDIS_HOST = "redis-secondary" -REDIS_PORT = 6379 -REDIS_PASSWORD = "" -ANSIBLE_API_HOSTNAME = "http://pulp-api-secondary:24817" -ANSIBLE_CONTENT_HOSTNAME = "http://pulp-content-secondary:24816/pulp/content" -ALLOWED_IMPORT_PATHS = ["/tmp"] -ALLOWED_EXPORT_PATHS = ["/tmp"] -TOKEN_SERVER = "http://pulp-api-secondary:24817/token/" -TOKEN_AUTH_DISABLED = False -TOKEN_SIGNATURE_ALGORITHM = "ES256" -PUBLIC_KEY_PATH = "/etc/pulp/keys/container_auth_public_key.pem" -PRIVATE_KEY_PATH = "/etc/pulp/keys/container_auth_private_key.pem" -ANALYTICS = False -STATIC_ROOT = "/var/lib/operator/static/" -ALLOWED_HOSTS = ['pulp-web-secondary:8080', 'localhost', '127.0.0.1', '[::1]'] \ No newline at end of file diff --git a/docker/simple-cluster.yml b/docker/simple-cluster.yml index e51b97a..8a5cdf6 100644 --- a/docker/simple-cluster.yml +++ b/docker/simple-cluster.yml @@ -100,6 +100,26 @@ services: networks: - pulp-net + pulp-manager-rq-dashboard: + build: .. + entrypoint: ["/bin/sh", "-c"] + command: ["/opt/venv/bin/rq-dashboard --bind 0.0.0.0 --port 9181 --redis-url redis://redis-manager:6379"] + ports: + - "9181:9181" + environment: + DB_HOSTNAME: mariadb + DB_NAME: pulp_manager + DB_USER: pulp-manager + DB_PASSWORD: pulp-manager + JWT_SECRET: test_secret + PULP_MANAGER_CONFIG_PATH: /pulp_manager/docker/simple/config.ini + PULP_SYNC_CONFIG_PATH: /pulp_manager/docker/simple/pulp-config.yml + Is_local: true + depends_on: + - redis-manager + networks: + - pulp-net + pulp-manager-scheduler: build: .. entrypoint: ["/bin/sh", "-c"] diff --git a/local_config.ini b/local_config.ini deleted file mode 100644 index bce7054..0000000 --- a/local_config.ini +++ /dev/null @@ -1,43 +0,0 @@ -;[ca] -;root_ca_file_path="" - -[auth] -method=ldap -use_ssl=true -ldap_servers=dc.example.com -base_dn=DC=example,DC=com -default_domain=example.com -jwt_algorithm=HS256 -jwt_token_lifetime_mins=480 -admin_group=pulpmaster-rw - -[pulp] -deb_signing_service=pulp_deb -banned_package_regex=bannedexample|another -internal_domains=example.com -git_repo_config=https://git.example.com/Pulp-Repo-Config -git_repo_config_dir=repo_config -password=password -internal_package_prefix=corp_ -package_name_replacement_pattern= -package_name_replacement_rule= -;may need to be false if using http proxy to remote -remote_tls_validation=true - -[redis] -host=redis -port=6379 -db=0 -max_page_size=24 - -[remotes] -sock_connect_timeout=120.0 -sock_read_timeout=600.0 - -[paging] -default_page_size=50 -max_page_size=20000 - -[vault] -vault_addr=http://127.0.0.1:8200 -repo_secret_namespace=cle-secrets-common-dev \ No newline at end of file diff --git a/local_pulp_config.yml b/local_pulp_config.yml deleted file mode 100644 index 0243892..0000000 --- a/local_pulp_config.yml +++ /dev/null @@ -1,23 +0,0 @@ -pulp_servers: - pulp-web:8080: - credentials: local - repo_config_registration: - schedule: "0,15,30,45 * * * *" - max_runtime: "20m" - repo_groups: - external_repos: - schedule: "00 13 * * *" - max_concurrent_syncs: 2 - max_runtime: "6h" - snapshot_support: - max_concurrent_snapshots: 2 - - -credentials: - local: - username: admin - vault_service_account_mount: service-accounts # optional: Password is loaded from ini file. - -repo_groups: - external_repos: - regex_include: "^ext" diff --git a/pulp_manager/app/routers/v1/pulp_servers.py b/pulp_manager/app/routers/v1/pulp_servers.py index f33e530..70a1bd0 100644 --- a/pulp_manager/app/routers/v1/pulp_servers.py +++ b/pulp_manager/app/routers/v1/pulp_servers.py @@ -357,9 +357,6 @@ def snapshot_repos( name="pulp_servers_v1:sync_repos", response_model=Task, status_code=201, - dependencies=[ - Depends(JWTBearer(allowed_groups=CONFIG["auth"]["admin_group"].split(","))) - ], ) def sync_repos(id: int, sync_config: PulpServerSyncConfig, db: get_session = Depends()): """Queues a repo sync job against the specified pulp server""" diff --git a/setup-demo.sh b/setup-demo.sh index 13441c1..fd1aa01 100755 --- a/setup-demo.sh +++ b/setup-demo.sh @@ -51,75 +51,128 @@ wait_for_task() { done } -echo "" -echo "Step 1: Creating External Repository (ext-small-repo)" -echo "====================================================" - -# Create external repository -if check_exists "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/" "ext-small-repo"; then - echo " ✅ Repository 'ext-small-repo' already exists" - EXT_REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/?name=ext-small-repo") - EXT_REPO_HREF=$(echo "$EXT_REPO_RESPONSE" | jq -r '.results[0].pulp_href') -else - echo " 📦 Creating repository 'ext-small-repo'..." - EXT_REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/" \ - -H "Content-Type: application/json" \ - -d '{"name": "ext-small-repo", "description": "External small Debian testing repository"}') - EXT_REPO_HREF=$(echo "$EXT_REPO_RESPONSE" | jq -r '.pulp_href') - echo " ✅ Repository created: $EXT_REPO_HREF" -fi +# Function to setup repository and remote on a server +setup_repo_and_remote() { + local server="$1" + local repo_name="$2" + local repo_desc="$3" + local remote_name="$4" + local remote_url="$5" + local distributions="$6" + local components="$7" + local architectures="$8" -# Create remote for Debian testing with limited components -if check_exists "$PULP_PRIMARY/pulp/api/v3/remotes/deb/apt/" "debian-testing-remote"; then - echo " ✅ Remote 'debian-testing-remote' already exists" - REMOTE_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY/pulp/api/v3/remotes/deb/apt/?name=debian-testing-remote") - REMOTE_HREF=$(echo "$REMOTE_RESPONSE" | jq -r '.results[0].pulp_href') -else - echo " 🌐 Creating remote for Debian testing (limited components)..." - REMOTE_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/remotes/deb/apt/" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "debian-testing-remote", - "url": "http://deb.debian.org/debian/", - "distributions": "testing", - "components": "main", - "architectures": "amd64", - "sync_sources": false, - "sync_udebs": false, - "sync_installer": false - }') - REMOTE_HREF=$(echo "$REMOTE_RESPONSE" | jq -r '.pulp_href') - echo " ✅ Remote created: $REMOTE_HREF" -fi + echo " 📦 Setting up $repo_name on $(basename $server)..." -# Associate remote with repository -echo " 🔗 Associating remote with repository..." -REPO_UPDATE_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X PATCH "$PULP_PRIMARY$EXT_REPO_HREF" \ - -H "Content-Type: application/json" \ - -d "{\"remote\": \"$REMOTE_HREF\"}") + # Create repository + if check_exists "$server/pulp/api/v3/repositories/deb/apt/" "$repo_name"; then + echo " ✅ Repository '$repo_name' already exists" + REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$server/pulp/api/v3/repositories/deb/apt/?name=$repo_name") + REPO_HREF=$(echo "$REPO_RESPONSE" | jq -r '.results[0].pulp_href') + else + echo " 📦 Creating repository '$repo_name'..." + REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$server/pulp/api/v3/repositories/deb/apt/" \ + -H "Content-Type: application/json" \ + -d "{\"name\": \"$repo_name\", \"description\": \"$repo_desc\"}") + REPO_HREF=$(echo "$REPO_RESPONSE" | jq -r '.pulp_href') + echo " ✅ Repository created: $REPO_HREF" + fi -if echo "$REPO_UPDATE_RESPONSE" | jq -e '.task' > /dev/null; then - TASK_HREF=$(echo "$REPO_UPDATE_RESPONSE" | jq -r '.task') - wait_for_task "$TASK_HREF" "$PULP_PRIMARY" -fi + # Only create remotes and associate them for secondary server (when remote_url is provided) + if [ -n "$remote_url" ]; then + # Create remote + if check_exists "$server/pulp/api/v3/remotes/deb/apt/" "$remote_name"; then + echo " ✅ Remote '$remote_name' already exists" + REMOTE_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$server/pulp/api/v3/remotes/deb/apt/?name=$remote_name") + REMOTE_HREF=$(echo "$REMOTE_RESPONSE" | jq -r '.results[0].pulp_href') + else + echo " 🌐 Creating remote '$remote_name'..." + remote_data="{\"name\": \"$remote_name\", \"url\": \"$remote_url\", \"distributions\": \"$distributions\"" + if [ -n "$components" ]; then + remote_data="$remote_data, \"components\": \"$components\"" + fi + if [ -n "$architectures" ]; then + remote_data="$remote_data, \"architectures\": \"$architectures\"" + fi + remote_data="$remote_data}" + + REMOTE_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$server/pulp/api/v3/remotes/deb/apt/" \ + -H "Content-Type: application/json" \ + -d "$remote_data") + REMOTE_HREF=$(echo "$REMOTE_RESPONSE" | jq -r '.pulp_href') + echo " ✅ Remote created: $REMOTE_HREF" + fi + + # Associate remote with repository + echo " 🔗 Associating remote with repository..." + REPO_UPDATE_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X PATCH "$server$REPO_HREF" \ + -H "Content-Type: application/json" \ + -d "{\"remote\": \"$REMOTE_HREF\"}") + + if echo "$REPO_UPDATE_RESPONSE" | jq -e '.task' > /dev/null; then + TASK_HREF=$(echo "$REPO_UPDATE_RESPONSE" | jq -r '.task') + wait_for_task "$TASK_HREF" "$server" + fi + fi + + echo "REPO_HREF_${repo_name//-/_}=$REPO_HREF" + + # Export variables for later use + export "REPO_HREF_${repo_name//-/_}"="$REPO_HREF" + if [ -n "$remote_url" ]; then + export "REMOTE_HREF_${repo_name//-/_}"="$REMOTE_HREF" + fi +} + +# Function to update pulp-manager database with remote associations +update_pulp_manager_db() { + local server_id="$1" + local repo_name="$2" + local remote_href="$3" + + echo " 🔄 Updating pulp-manager database for $repo_name on server $server_id..." + + # Wait for pulp-manager to discover the repository first + sleep 5 + + # Get the pulp-manager repo ID by querying repos for the server + local pm_repo_response=$(curl -s "http://localhost:8080/v1/pulp_servers/$server_id/repos") + local pm_repo_id=$(echo "$pm_repo_response" | jq -r ".items[] | select(.name == \"$repo_name\") | .id") + + if [ -n "$pm_repo_id" ] && [ "$pm_repo_id" != "null" ]; then + # Update the database directly + docker exec docker-mariadb-1 mariadb -u pulp-manager -ppulp-manager pulp_manager \ + -e "UPDATE pulp_server_repos SET remote_href = '$remote_href' WHERE id = $pm_repo_id;" + echo " ✅ Updated pulp-manager database: repo ID $pm_repo_id -> $remote_href" + else + echo " ⚠️ Could not find repo $repo_name in pulp-manager database for server $server_id" + echo " Available repos: $(echo "$pm_repo_response" | jq -r '.items[].name' | tr '\n' ' ')" + fi +} echo "" -echo "Step 2: Creating Internal Repository (int-demo-packages)" -echo "=======================================================" - -# Create internal repository -if check_exists "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/" "int-demo-packages"; then - echo " ✅ Repository 'int-demo-packages' already exists" - INT_REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/?name=int-demo-packages") - INT_REPO_HREF=$(echo "$INT_REPO_RESPONSE" | jq -r '.results[0].pulp_href') -else - echo " 📦 Creating repository 'int-demo-packages'..." - INT_REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/" \ - -H "Content-Type: application/json" \ - -d '{"name": "int-demo-packages", "description": "Internal demo repository"}') - INT_REPO_HREF=$(echo "$INT_REPO_RESPONSE" | jq -r '.pulp_href') - echo " ✅ Repository created: $INT_REPO_HREF" -fi +echo "Step 1: Setting up Primary Server Repositories" +echo "==============================================" + +# Setup external repo on primary with upstream remote +setup_repo_and_remote "$PULP_PRIMARY" "ext-small-repo" "External small Debian testing repository" \ + "debian-testing-remote" "http://deb.debian.org/debian/" "testing" "main" "amd64" +eval $(echo "REPO_HREF_ext_small_repo=$REPO_HREF") + +# Setup internal repo on primary (no remote needed) +setup_repo_and_remote "$PULP_PRIMARY" "int-demo-packages" "Internal demo repository" "" "" "" "" "" +eval $(echo "REPO_HREF_int_demo_packages=$REPO_HREF") + +echo "" +echo "Step 2: Setting up Secondary Server Repositories" +echo "===============================================" + +# Setup repos on secondary that sync from primary +setup_repo_and_remote "$PULP_SECONDARY" "int-demo-packages" "Internal demo packages synced from primary" \ + "int-demo-remote" "http://docker-pulp-primary-1/pulp/content/int-demo-packages/" "stable" "" "" + +setup_repo_and_remote "$PULP_SECONDARY" "ext-small-repo" "External small repo synced from primary" \ + "ext-small-remote" "http://docker-pulp-primary-1/pulp/content/ext-small-repo/" "testing" "main" "amd64" echo "" echo "Step 3: Uploading Demo Package to Internal Repository" @@ -241,6 +294,14 @@ else echo " ✅ Internal distribution created" fi +echo "" +echo "Step 6: Updating Pulp Manager Database" +echo "======================================" + +# Update pulp-manager database with remote associations for secondary server (ID=2) +update_pulp_manager_db "2" "int-demo-packages" "$REMOTE_HREF_int_demo_packages" +update_pulp_manager_db "2" "ext-small-repo" "$REMOTE_HREF_ext_small_repo" + echo "" echo "🎉 Demo Setup Complete!" echo "======================" @@ -249,9 +310,11 @@ echo "Available repositories:" echo " 📦 ext-small-repo (external): $PULP_PRIMARY/pulp/content/ext-small-repo/" echo " 📦 int-demo-packages (internal): $PULP_PRIMARY/pulp/content/int-demo-packages/" echo "" -echo "Next steps:" -echo " 1. Sync external repository: curl -u admin:password -X POST '$PULP_PRIMARY${EXT_REPO_HREF}sync/' -H 'Content-Type: application/json' -d '{\"remote\": \"$REMOTE_HREF\"}'" -echo " 2. Check sync status via Pulp Manager API" -echo " 3. Configure secondary server to sync from primary" +echo "Pulp Manager sync commands:" +echo " # Sync internal repositories:" +echo " curl -X POST 'http://localhost:8080/v1/pulp_servers/2/sync_repos' -H 'Content-Type: application/json' -d '{\"max_runtime\": \"3600\", \"max_concurrent_syncs\": 5, \"regex_include\": \"int-.*\", \"regex_exclude\": \"\"}'" +echo "" +echo " # Sync external repositories:" +echo " curl -X POST 'http://localhost:8080/v1/pulp_servers/2/sync_repos' -H 'Content-Type: application/json' -d '{\"max_runtime\": \"3600\", \"max_concurrent_syncs\": 5, \"regex_include\": \"ext-.*\", \"regex_exclude\": \"\"}'" echo "" -echo "View repositories: curl -u admin:password '$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/' | jq '.results[] | {name, pulp_href}'" \ No newline at end of file +echo "Monitor tasks: http://localhost:9181" \ No newline at end of file From fbda7f14dade024e54cbc9758c60b2a5a5155694 Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Thu, 25 Sep 2025 13:57:20 -0400 Subject: [PATCH 06/24] Restore test configs for unit Signed-off-by: Geoff Wilson --- pytest.ini | 8 ++++++-- test_config.ini | 48 ++++++++++++++++++++++++++++++++++++++++++++ test_pulp_config.yml | 14 +++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 test_config.ini create mode 100644 test_pulp_config.yml diff --git a/pytest.ini b/pytest.ini index 5a6e0d7..1731114 100644 --- a/pytest.ini +++ b/pytest.ini @@ -6,5 +6,9 @@ norecursedirs = pulp3_bindings hashi_vault_client .git .tox dist build *.egg ven env = JWT_SECRET=test_secret PULP_MANAGER_SKIP_PARSER_CONFIG=1 - PULP_MANAGER_CONFIG_PATH=./local_config.ini - PULP_SYNC_CONFIG_PATH=./local_pulp_config.yml + PULP_MANAGER_CONFIG_PATH=./test_config.ini + PULP_SYNC_CONFIG_PATH=./test_pulp_config.yml + DB_HOSTNAME=localhost + DB_NAME=test_pulp_manager + DB_USER=test_user + DB_PASSWORD=test_password diff --git a/test_config.ini b/test_config.ini new file mode 100644 index 0000000..0182984 --- /dev/null +++ b/test_config.ini @@ -0,0 +1,48 @@ +[database] +user=test_user +password=test_password +host=localhost +port=3306 +db_name=test_pulp_manager + +[auth] +method=ldap +use_ssl=false +ldap_servers=server:389 +base_dn=ou=people,dc=pulpproject,dc=com +default_domain=pulpproject.com +jwt_algorithm=HS256 +jwt_token_lifetime_mins=480 +admin_group=pulpmaster-rw + +[pulp] +deb_signing_service=pulp_deb +banned_package_regex=bannedexample|another +internal_domains=pulp-primary,pulp-secondary +git_repo_config=https://github.com/example/repo-config.git +git_repo_config_dir=repo_config +password=password +internal_package_prefix=int_ +package_name_replacement_pattern= +package_name_replacement_rule= +remote_tls_validation=true + +[redis] +host=localhost +port=6379 +db=0 +max_page_size=24 + +[remotes] +sock_connect_timeout=120.0 +sock_read_timeout=600.0 + +[paging] +default_page_size=50 +max_page_size=20000 + +[vault] +repo_secret_namespace=test-namespace +enabled=false +url=http://localhost:8200 +vault_addr=http://localhost:8200 \ No newline at end of file diff --git a/test_pulp_config.yml b/test_pulp_config.yml new file mode 100644 index 0000000..041d122 --- /dev/null +++ b/test_pulp_config.yml @@ -0,0 +1,14 @@ +credentials: + pulp_servers: + - pulp_server: http://localhost:8000 + user: admin + password: password + +repo_groups: + - name: test-group + sync_schedule: "0 */6 * * *" + +pulp_servers: + - pulp_server: http://localhost:8000 + repo_groups: + - test-group \ No newline at end of file From 6863dfb391b68c063e6f4cab349c8afade248269 Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Thu, 25 Sep 2025 14:19:56 -0400 Subject: [PATCH 07/24] Fix remaining test failure (due to internal domains mismatch) Signed-off-by: Geoff Wilson --- pulp_manager/tests/unit/services/test_repo_syncher.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pulp_manager/tests/unit/services/test_repo_syncher.py b/pulp_manager/tests/unit/services/test_repo_syncher.py index 67cd293..dad99e1 100644 --- a/pulp_manager/tests/unit/services/test_repo_syncher.py +++ b/pulp_manager/tests/unit/services/test_repo_syncher.py @@ -439,8 +439,8 @@ def test_start_remove_banned_packages_skip1(self, mock_find_packages_to_remove, result = self.repo_syncher._start_remove_banned_packages(task) assert result == False - @patch("pulp_manager.app.services.repo_syncher.get_repo") - @patch("pulp_manager.app.services.repo_syncher.get_remote") + @patch("pulp_manager.app.services.repo_syncher.get_repo", autospec=True) + @patch("pulp_manager.app.services.repo_syncher.get_remote", autospec=True) def test_start_remove_banned_packages_skip2(self, mock_get_remote, mock_get_repo): """Tests that if the feed was from an internal domain then no checks are done for banned packages @@ -449,13 +449,14 @@ def test_start_remove_banned_packages_skip2(self, mock_get_remote, mock_get_repo mock_get_repo.return_value = RpmRepository(**{ "pulp_href": "/pulp/api/v3/repositories/rpm/rpm/123", "name": "test-rpm", - "latest_version_href": "/pulp/api/v3/repositories/rpm/rpm/123/versions/1" + "latest_version_href": "/pulp/api/v3/repositories/rpm/rpm/123/versions/1", + "remote": "/pulp/api/v3/remotes/rpm/rpm/123" }) mock_get_remote.return_value = RpmRemote(**{ "pulp_href": "/pulp/api/v3/remotes/rpm/rpm/123", "name": "test-rpm", - "url": "https://pulp.example.com/", + "url": "https://pulp-primary/pulp/content/test-repo/", "policy": "immediate" }) From bc7e4b6d62810700d9792cb558c4c216740265ce Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Fri, 26 Sep 2025 10:44:32 -0400 Subject: [PATCH 08/24] fix setup script errors when cluster unavailable --- setup-demo.sh | 49 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/setup-demo.sh b/setup-demo.sh index fd1aa01..ed3d7a6 100755 --- a/setup-demo.sh +++ b/setup-demo.sh @@ -7,16 +7,50 @@ PULP_SECONDARY="http://localhost:8001" PULP_USER="admin" PULP_PASS="password" -echo "🚀 Setting up Pulp Demo Environment" -echo "==================================" +echo "Setting up Pulp Demo Environment" +echo "=================================" + +# Function to check if a server is running and accessible +check_server_running() { + local server="$1" + local server_name="$2" + + echo "Checking if $server_name is running..." + + if ! curl -s --connect-timeout 5 "$server/pulp/api/v3/status/" > /dev/null 2>&1; then + echo "ERROR: $server_name at $server is not accessible!" + echo "" + echo "To fix this issue:" + echo " 1. Start the Pulp cluster: make run-cluster" + echo " 2. Wait for services to be healthy (may take 1-2 minutes)" + echo " 3. Then run: make setup-demo" + echo "" + return 1 + fi + + echo "$server_name is running and accessible" + return 0 +} # Function to check if a resource exists by name check_exists() { local url="$1" local name="$2" - local response=$(curl -s -u $PULP_USER:$PULP_PASS "$url?name=$name") - local count=$(echo "$response" | jq -r '.count // 0') - [ "$count" -gt 0 ] + local response=$(curl -s -u $PULP_USER:$PULP_PASS "$url?name=$name" 2>/dev/null) + + # Check if curl failed or returned empty response + if [ -z "$response" ] || ! echo "$response" | jq . > /dev/null 2>&1; then + echo "Warning: Could not connect to server or invalid JSON response" + return 1 + fi + + local count=$(echo "$response" | jq -r '.count // 0' 2>/dev/null) + # Ensure count is a valid number + if [[ "$count" =~ ^[0-9]+$ ]] && [ "$count" -gt 0 ]; then + return 0 + else + return 1 + fi } # Function to wait for task completion @@ -150,6 +184,11 @@ update_pulp_manager_db() { fi } +echo "" +echo "Checking server connectivity..." +check_server_running "$PULP_PRIMARY" "Pulp Primary" +check_server_running "$PULP_SECONDARY" "Pulp Secondary" + echo "" echo "Step 1: Setting up Primary Server Repositories" echo "==============================================" From a7f2f90e64424f73b71440c3567c65a35e3c2a89 Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Mon, 29 Sep 2025 12:08:05 -0400 Subject: [PATCH 09/24] Changes for running tests in devcontainer (only supported method) Signed-off-by: Geoff Wilson --- .devcontainer/docker-compose.yml | 4 +- .../test_config.ini | 10 +-- Makefile | 19 +++++- README.md | 7 +- pytest.ini | 8 +-- setup-demo.sh | 68 +++++++++---------- test_pulp_config.yml | 14 ---- 7 files changed, 63 insertions(+), 67 deletions(-) rename test_config.ini => .devcontainer/test_config.ini (90%) delete mode 100644 test_pulp_config.yml diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 9ff4ef9..0bee1e6 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -15,8 +15,8 @@ services: DB_USER: pulp-manager DB_PASSWORD: pulp-manager JWT_SECRET: test_secret - PULP_MANAGER_CONFIG_PATH: /workspace/local_config.ini - PULP_SYNC_CONFIG_PATH: /workspace/local_pulp_config.yml + PULP_MANAGER_CONFIG_PATH: /workspace/docker/simple/config.ini + PULP_SYNC_CONFIG_PATH: /workspace/docker/simple/pulp_config.yml Is_local: "true" networks: - pulp-devcontainer-net diff --git a/test_config.ini b/.devcontainer/test_config.ini similarity index 90% rename from test_config.ini rename to .devcontainer/test_config.ini index 0182984..f82c2c2 100644 --- a/test_config.ini +++ b/.devcontainer/test_config.ini @@ -1,9 +1,9 @@ [database] -user=test_user -password=test_password -host=localhost +user=pulp-manager +password=pulp-manager +host=mariadb port=3306 -db_name=test_pulp_manager +db_name=pulp_manager [auth] method=ldap @@ -28,7 +28,7 @@ package_name_replacement_rule= remote_tls_validation=true [redis] -host=localhost +host=redis-manager port=6379 db=0 max_page_size=24 diff --git a/Makefile b/Makefile index d50138e..b0385e7 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,6 @@ h help: "l|lint" "Run lint" \ "c|cover" "Run coverage for all tests" \ "venv" "Create virtualenv" \ - "bdist" "Create wheel file" \ "clean" "Clean workspace" \ "setup-keys" "Generates keys for use in local cluster" \ "run-pulp3" "Start Pulp 3 locally with Docker Compose" \ @@ -26,12 +25,26 @@ l lint: venv @echo "# pylint"; \ ./venv/bin/pylint --rcfile ./pylint.rc pulp_manager/ +check-devcontainer: + @if [ -z "$$Is_local" ] && [ -z "$$DEVCONTAINER" ]; then \ + echo "ERROR: Tests must be run in devcontainer environment!"; \ + echo ""; \ + echo "To run tests:"; \ + echo " 1. Open VS Code"; \ + echo " 2. Use Command Palette (Cmd/Ctrl+Shift+P)"; \ + echo " 3. Select 'Dev Containers: Reopen in Container'"; \ + echo " 4. Wait for container to build"; \ + echo " 5. Run: make t"; \ + echo ""; \ + exit 1; \ + fi + .PHONY : t test -t test: venv +t test: venv check-devcontainer @./venv/bin/pytest -v .PHONY : c cover -c cover: venv +c cover: venv check-devcontainer @. venv/bin/activate; \ coverage erase; \ coverage run --source=. --omit=pulp_manager/tests/unit/mock_repository.py -m pytest -v && coverage report --fail-under=90; \ diff --git a/README.md b/README.md index a9b548c..2a529b2 100644 --- a/README.md +++ b/README.md @@ -130,14 +130,15 @@ components: 1. **Using DevContainers (Recommended)** ```bash - # Open in VS Code and select "Reopen in Container" - # Or use the CLI: + # Open in VS Code and select action "Dev Containers: Reopen in Container" + # Or use the Dev Container CLI: devcontainer up --workspace-folder . ``` 2. **Manual Setup** ```bash - make run-pulp-manager + make run-cluster + make setup-cluster ``` For detailed development setup, see the [Development diff --git a/pytest.ini b/pytest.ini index 1731114..d3ff18a 100644 --- a/pytest.ini +++ b/pytest.ini @@ -6,9 +6,5 @@ norecursedirs = pulp3_bindings hashi_vault_client .git .tox dist build *.egg ven env = JWT_SECRET=test_secret PULP_MANAGER_SKIP_PARSER_CONFIG=1 - PULP_MANAGER_CONFIG_PATH=./test_config.ini - PULP_SYNC_CONFIG_PATH=./test_pulp_config.yml - DB_HOSTNAME=localhost - DB_NAME=test_pulp_manager - DB_USER=test_user - DB_PASSWORD=test_password + PULP_MANAGER_CONFIG_PATH=./.devcontainer/test_config.ini + PULP_SYNC_CONFIG_PATH=./docker/simple/pulp-config.yml diff --git a/setup-demo.sh b/setup-demo.sh index ed3d7a6..711280e 100755 --- a/setup-demo.sh +++ b/setup-demo.sh @@ -65,20 +65,20 @@ wait_for_task() { case "$state" in "completed") - echo " ✅ Task completed successfully" + echo " Task completed successfully" return 0 ;; "failed") - echo " ❌ Task failed!" + echo " Task failed!" echo " Error: $(echo "$task_result" | jq -r '.error.description // .error')" return 1 ;; "running"|"waiting") - echo " ⏳ Task $state, waiting..." + echo " Task $state, waiting..." sleep 2 ;; *) - echo " ⏳ Task in state: $state, waiting..." + echo " Task in state: $state, waiting..." sleep 2 ;; esac @@ -96,31 +96,31 @@ setup_repo_and_remote() { local components="$7" local architectures="$8" - echo " 📦 Setting up $repo_name on $(basename $server)..." + echo " Setting up $repo_name on $(basename $server)..." # Create repository if check_exists "$server/pulp/api/v3/repositories/deb/apt/" "$repo_name"; then - echo " ✅ Repository '$repo_name' already exists" + echo " Repository '$repo_name' already exists" REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$server/pulp/api/v3/repositories/deb/apt/?name=$repo_name") REPO_HREF=$(echo "$REPO_RESPONSE" | jq -r '.results[0].pulp_href') else - echo " 📦 Creating repository '$repo_name'..." + echo " Creating repository '$repo_name'..." REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$server/pulp/api/v3/repositories/deb/apt/" \ -H "Content-Type: application/json" \ -d "{\"name\": \"$repo_name\", \"description\": \"$repo_desc\"}") REPO_HREF=$(echo "$REPO_RESPONSE" | jq -r '.pulp_href') - echo " ✅ Repository created: $REPO_HREF" + echo " Repository created: $REPO_HREF" fi # Only create remotes and associate them for secondary server (when remote_url is provided) if [ -n "$remote_url" ]; then # Create remote if check_exists "$server/pulp/api/v3/remotes/deb/apt/" "$remote_name"; then - echo " ✅ Remote '$remote_name' already exists" + echo " Remote '$remote_name' already exists" REMOTE_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$server/pulp/api/v3/remotes/deb/apt/?name=$remote_name") REMOTE_HREF=$(echo "$REMOTE_RESPONSE" | jq -r '.results[0].pulp_href') else - echo " 🌐 Creating remote '$remote_name'..." + echo " Creating remote '$remote_name'..." remote_data="{\"name\": \"$remote_name\", \"url\": \"$remote_url\", \"distributions\": \"$distributions\"" if [ -n "$components" ]; then remote_data="$remote_data, \"components\": \"$components\"" @@ -134,11 +134,11 @@ setup_repo_and_remote() { -H "Content-Type: application/json" \ -d "$remote_data") REMOTE_HREF=$(echo "$REMOTE_RESPONSE" | jq -r '.pulp_href') - echo " ✅ Remote created: $REMOTE_HREF" + echo " Remote created: $REMOTE_HREF" fi # Associate remote with repository - echo " 🔗 Associating remote with repository..." + echo " Associating remote with repository..." REPO_UPDATE_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X PATCH "$server$REPO_HREF" \ -H "Content-Type: application/json" \ -d "{\"remote\": \"$REMOTE_HREF\"}") @@ -164,7 +164,7 @@ update_pulp_manager_db() { local repo_name="$2" local remote_href="$3" - echo " 🔄 Updating pulp-manager database for $repo_name on server $server_id..." + echo " Updating pulp-manager database for $repo_name on server $server_id..." # Wait for pulp-manager to discover the repository first sleep 5 @@ -177,9 +177,9 @@ update_pulp_manager_db() { # Update the database directly docker exec docker-mariadb-1 mariadb -u pulp-manager -ppulp-manager pulp_manager \ -e "UPDATE pulp_server_repos SET remote_href = '$remote_href' WHERE id = $pm_repo_id;" - echo " ✅ Updated pulp-manager database: repo ID $pm_repo_id -> $remote_href" + echo " Updated pulp-manager database: repo ID $pm_repo_id -> $remote_href" else - echo " ⚠️ Could not find repo $repo_name in pulp-manager database for server $server_id" + echo " Could not find repo $repo_name in pulp-manager database for server $server_id" echo " Available repos: $(echo "$pm_repo_response" | jq -r '.items[].name' | tr '\n' ' ')" fi } @@ -222,9 +222,9 @@ INT_REPO_CONTENT=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY${INT_REPO_HRE HELLO_PKG_EXISTS=$(echo "$INT_REPO_CONTENT" | jq -r '.results[] | select(.summary and (.summary | contains("hello"))) | .pulp_href' | head -n1) if [ -n "$HELLO_PKG_EXISTS" ] && [ "$HELLO_PKG_EXISTS" != "null" ]; then - echo " ✅ Demo package already exists in repository" + echo " Demo package already exists in repository" else - echo " 📤 Uploading demo package..." + echo " Uploading demo package..." CONTENT_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/content/deb/packages/" \ -H "Content-Type: multipart/form-data" \ -F "file=@assets/packages/hello_2.10-2_amd64.deb") @@ -235,10 +235,10 @@ else # Get content href from completed task TASK_RESULT=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY$UPLOAD_TASK_HREF") CONTENT_HREF=$(echo "$TASK_RESULT" | jq -r '.created_resources[0]') - echo " 📦 Content created: $CONTENT_HREF" + echo " Content created: $CONTENT_HREF" # Add content to repository - echo " ➕ Adding content to repository..." + echo " Adding content to repository..." MODIFY_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY${INT_REPO_HREF}modify/" \ -H "Content-Type: application/json" \ -d "{\"add_content_units\": [\"$CONTENT_HREF\"]}") @@ -252,7 +252,7 @@ echo "Step 4: Creating Publications" echo "=============================" # Create publication for external repository -echo " 📰 Creating publication for ext-small-repo..." +echo " Creating publication for ext-small-repo..." EXT_PUB_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/publications/deb/apt/" \ -H "Content-Type: application/json" \ -d "{\"repository\": \"$EXT_REPO_HREF\"}") @@ -264,14 +264,14 @@ if echo "$EXT_PUB_RESPONSE" | jq -e '.task' > /dev/null; then # Get publication href PUB_TASK_RESULT=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY$EXT_PUB_TASK") EXT_PUB_HREF=$(echo "$PUB_TASK_RESULT" | jq -r '.created_resources[0]') - echo " ✅ External publication created: $EXT_PUB_HREF" + echo " External publication created: $EXT_PUB_HREF" else EXT_PUB_HREF=$(echo "$EXT_PUB_RESPONSE" | jq -r '.pulp_href') - echo " ✅ External publication exists: $EXT_PUB_HREF" + echo " External publication exists: $EXT_PUB_HREF" fi # Create publication for internal repository -echo " 📰 Creating publication for int-demo-packages..." +echo " Creating publication for int-demo-packages..." INT_PUB_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/publications/deb/apt/" \ -H "Content-Type: application/json" \ -d "{\"repository\": \"$INT_REPO_HREF\"}") @@ -283,10 +283,10 @@ if echo "$INT_PUB_RESPONSE" | jq -e '.task' > /dev/null; then # Get publication href PUB_TASK_RESULT=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY$INT_PUB_TASK") INT_PUB_HREF=$(echo "$PUB_TASK_RESULT" | jq -r '.created_resources[0]') - echo " ✅ Internal publication created: $INT_PUB_HREF" + echo " Internal publication created: $INT_PUB_HREF" else INT_PUB_HREF=$(echo "$INT_PUB_RESPONSE" | jq -r '.pulp_href') - echo " ✅ Internal publication exists: $INT_PUB_HREF" + echo " Internal publication exists: $INT_PUB_HREF" fi echo "" @@ -295,9 +295,9 @@ echo "==============================" # Create distribution for external repository if check_exists "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/" "ext-small-repo"; then - echo " ✅ Distribution 'ext-small-repo' already exists" + echo " Distribution 'ext-small-repo' already exists" else - echo " 🌐 Creating distribution for ext-small-repo..." + echo " Creating distribution for ext-small-repo..." EXT_DIST_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/" \ -H "Content-Type: application/json" \ -d "{ @@ -310,14 +310,14 @@ else EXT_DIST_TASK=$(echo "$EXT_DIST_RESPONSE" | jq -r '.task') wait_for_task "$EXT_DIST_TASK" "$PULP_PRIMARY" fi - echo " ✅ External distribution created" + echo " External distribution created" fi # Create distribution for internal repository if check_exists "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/" "int-demo-packages"; then - echo " ✅ Distribution 'int-demo-packages' already exists" + echo " Distribution 'int-demo-packages' already exists" else - echo " 🌐 Creating distribution for int-demo-packages..." + echo " Creating distribution for int-demo-packages..." INT_DIST_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/" \ -H "Content-Type: application/json" \ -d "{ @@ -330,7 +330,7 @@ else INT_DIST_TASK=$(echo "$INT_DIST_RESPONSE" | jq -r '.task') wait_for_task "$INT_DIST_TASK" "$PULP_PRIMARY" fi - echo " ✅ Internal distribution created" + echo " Internal distribution created" fi echo "" @@ -342,12 +342,12 @@ update_pulp_manager_db "2" "int-demo-packages" "$REMOTE_HREF_int_demo_packages" update_pulp_manager_db "2" "ext-small-repo" "$REMOTE_HREF_ext_small_repo" echo "" -echo "🎉 Demo Setup Complete!" +echo " Demo Setup Complete!" echo "======================" echo "" echo "Available repositories:" -echo " 📦 ext-small-repo (external): $PULP_PRIMARY/pulp/content/ext-small-repo/" -echo " 📦 int-demo-packages (internal): $PULP_PRIMARY/pulp/content/int-demo-packages/" +echo " ext-small-repo (external): $PULP_PRIMARY/pulp/content/ext-small-repo/" +echo " int-demo-packages (internal): $PULP_PRIMARY/pulp/content/int-demo-packages/" echo "" echo "Pulp Manager sync commands:" echo " # Sync internal repositories:" diff --git a/test_pulp_config.yml b/test_pulp_config.yml deleted file mode 100644 index 041d122..0000000 --- a/test_pulp_config.yml +++ /dev/null @@ -1,14 +0,0 @@ -credentials: - pulp_servers: - - pulp_server: http://localhost:8000 - user: admin - password: password - -repo_groups: - - name: test-group - sync_schedule: "0 */6 * * *" - -pulp_servers: - - pulp_server: http://localhost:8000 - repo_groups: - - test-group \ No newline at end of file From 2a6f7ab0a2d5ef2dd791fe800aad8ebaf0bbce17 Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Mon, 29 Sep 2025 15:16:41 -0400 Subject: [PATCH 10/24] Signing service for demo cluster Signed-off-by: Geoff Wilson --- assets/scripts/signing_service.py | 0 docker/simple-cluster.yml | 19 +++++++++-- docker/simple/config.ini | 3 +- docker/simple/settings/settings.py | 2 +- setup-demo.sh | 53 +++++++++++++++++++++++++++--- 5 files changed, 69 insertions(+), 8 deletions(-) mode change 100644 => 100755 assets/scripts/signing_service.py diff --git a/assets/scripts/signing_service.py b/assets/scripts/signing_service.py old mode 100644 new mode 100755 diff --git a/docker/simple-cluster.yml b/docker/simple-cluster.yml index 8a5cdf6..6e27c23 100644 --- a/docker/simple-cluster.yml +++ b/docker/simple-cluster.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: pulp-primary: image: pulp/pulp:stable @@ -140,6 +138,23 @@ services: networks: - pulp-net + deb-signing-service: + image: python:3.11-slim + command: > + sh -c "apt-get update && apt-get install -y gnupg && + pip install flask && + python /app/signing_service.py" + volumes: + - "../assets/scripts/signing_service.py:/app/signing_service.py:z" + - "../assets/keys/gpg:/app/gpg:z" + environment: + - GNUPGHOME=/app/gpg + networks: + - pulp-net + ports: + - "8082:8080" + restart: unless-stopped + networks: pulp-net: external: true \ No newline at end of file diff --git a/docker/simple/config.ini b/docker/simple/config.ini index 582c665..519e994 100644 --- a/docker/simple/config.ini +++ b/docker/simple/config.ini @@ -16,7 +16,7 @@ jwt_token_lifetime_mins=480 admin_group=pulpmaster-rw [pulp] -# deb_signing_service=pulp_deb +deb_signing_service=deb_signing_service banned_package_regex=bannedexample|another internal_domains=pulp-primary,pulp-secondary git_repo_config_dir=repo_config @@ -25,6 +25,7 @@ internal_package_prefix=int_ package_name_replacement_pattern= package_name_replacement_rule= remote_tls_validation=false +use_https_for_sync=false [redis] host=redis-manager diff --git a/docker/simple/settings/settings.py b/docker/simple/settings/settings.py index 79e1714..582ece6 100644 --- a/docker/simple/settings/settings.py +++ b/docker/simple/settings/settings.py @@ -60,6 +60,6 @@ # Signing service configuration SIGNING_SERVICES = { 'deb_signing_service': { - 'SCRIPT': '/opt/deb_sign_script.sh', + 'SCRIPT': '/opt/scripts/deb_sign.sh', } } diff --git a/setup-demo.sh b/setup-demo.sh index 711280e..24a2d5b 100755 --- a/setup-demo.sh +++ b/setup-demo.sh @@ -189,6 +189,45 @@ echo "Checking server connectivity..." check_server_running "$PULP_PRIMARY" "Pulp Primary" check_server_running "$PULP_SECONDARY" "Pulp Secondary" +# Function to register signing service in Pulp +register_signing_service() { + local container_name="$1" + local server_name="$2" + + echo " Checking if signing service exists on $server_name..." + + # Check if signing service already exists in the database + existing=$(docker exec $container_name bash -c "pulpcore-manager shell -c \"from pulpcore.app.models import SigningService; print(SigningService.objects.filter(name='deb_signing_service').exists())\"" 2>/dev/null) + + if [ "$existing" = "True" ]; then + echo " Signing service 'deb_signing_service' already exists on $server_name" + else + echo " Creating signing service 'deb_signing_service' on $server_name..." + + # Get the GPG key ID from the mounted keyring + key_id=$(docker exec $container_name bash -c "GNUPGHOME=/opt/gpg gpg --list-secret-keys --with-colons 2>/dev/null | grep '^sec:' | cut -d: -f5 | head -1") + + if [ -n "$key_id" ]; then + # Add the signing service using the management command + docker exec $container_name bash -c "pulpcore-manager add-signing-service \ + 'deb_signing_service' \ + '/opt/scripts/deb_sign.sh' \ + '$key_id' \ + --gnupghome /opt/gpg" 2>/dev/null && \ + echo " Signing service created successfully on $server_name" || \ + echo " Failed to create signing service on $server_name" + else + echo " No GPG key found in /opt/gpg on $server_name" + fi + fi +} + +echo "" +echo "Setting up signing services..." +echo "==============================" +register_signing_service "docker-pulp-primary-1" "Pulp Primary" +register_signing_service "docker-pulp-secondary-1" "Pulp Secondary" + echo "" echo "Step 1: Setting up Primary Server Repositories" echo "==============================================" @@ -196,11 +235,11 @@ echo "==============================================" # Setup external repo on primary with upstream remote setup_repo_and_remote "$PULP_PRIMARY" "ext-small-repo" "External small Debian testing repository" \ "debian-testing-remote" "http://deb.debian.org/debian/" "testing" "main" "amd64" -eval $(echo "REPO_HREF_ext_small_repo=$REPO_HREF") +EXT_REPO_HREF=$REPO_HREF # Setup internal repo on primary (no remote needed) setup_repo_and_remote "$PULP_PRIMARY" "int-demo-packages" "Internal demo repository" "" "" "" "" "" -eval $(echo "REPO_HREF_int_demo_packages=$REPO_HREF") +INT_REPO_HREF=$REPO_HREF echo "" echo "Step 2: Setting up Secondary Server Repositories" @@ -218,8 +257,14 @@ echo "Step 3: Uploading Demo Package to Internal Repository" echo "=====================================================" # Check if package already exists in repository -INT_REPO_CONTENT=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY${INT_REPO_HREF}versions/1/content/") -HELLO_PKG_EXISTS=$(echo "$INT_REPO_CONTENT" | jq -r '.results[] | select(.summary and (.summary | contains("hello"))) | .pulp_href' | head -n1) +# First get the latest version href +LATEST_VERSION=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY${INT_REPO_HREF}versions/?limit=1&ordering=-number" | jq -r '.results[0].pulp_href' 2>/dev/null) +if [ -n "$LATEST_VERSION" ] && [ "$LATEST_VERSION" != "null" ]; then + INT_REPO_CONTENT=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY${LATEST_VERSION}content/?limit=100") + HELLO_PKG_EXISTS=$(echo "$INT_REPO_CONTENT" | jq -r '.results[] | select(.relative_path and (.relative_path | contains("hello"))) | .pulp_href' 2>/dev/null | head -n1) +else + HELLO_PKG_EXISTS="" +fi if [ -n "$HELLO_PKG_EXISTS" ] && [ "$HELLO_PKG_EXISTS" != "null" ]; then echo " Demo package already exists in repository" From 219d9155c5144829d0240185d745e19ebab56beb Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Tue, 30 Sep 2025 14:42:31 -0400 Subject: [PATCH 11/24] Restore db encryption key for demo Signed-off-by: Geoff Wilson --- docker/simple/settings/database_fields.symmetric.key | 1 + docker/simple/settings/settings.py | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 docker/simple/settings/database_fields.symmetric.key diff --git a/docker/simple/settings/database_fields.symmetric.key b/docker/simple/settings/database_fields.symmetric.key new file mode 100644 index 0000000..f73cd1f --- /dev/null +++ b/docker/simple/settings/database_fields.symmetric.key @@ -0,0 +1 @@ +XlN6mYFXLtvJ30fQXvR96UxTNf+sNHbDmAERi7CWRf0= diff --git a/docker/simple/settings/settings.py b/docker/simple/settings/settings.py index 582ece6..22c7c37 100644 --- a/docker/simple/settings/settings.py +++ b/docker/simple/settings/settings.py @@ -12,6 +12,9 @@ } } +# Database encryption key +DB_ENCRYPTION_KEY = "/etc/pulp/database_fields.symmetric.key" + # Cache settings for performance CACHE_ENABLED = True From 2e858410b8c7510a05b12c6ad533822915a79085 Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Wed, 1 Oct 2025 14:57:18 -0400 Subject: [PATCH 12/24] Added a new setting, "require_jwt_auth", for facilitating demo cluster (to remove directory server dependency); restored all other mainline code changes --- .devcontainer/test_config.ini | 2 + README.md | 4 ++ docker/simple-cluster.yml | 6 +- docker/simple/config.ini | 2 + .../simple/pulp-primary/containers/.gitkeep | 0 .../simple/pulp-secondary/containers/.gitkeep | 0 docker/simple/settings/settings-primary.py | 68 +++++++++++++++++++ docker/simple/settings/settings-secondary.py | 68 +++++++++++++++++++ .../app/repositories/table_repository.py | 17 ++--- pulp_manager/app/routers/v1/pulp_servers.py | 22 ++++-- pulp_manager/app/services/pulp_manager.py | 11 +-- .../tests/unit/services/test_repo_syncher.py | 9 ++- setup-demo.sh | 40 ++++++++++- 13 files changed, 214 insertions(+), 35 deletions(-) delete mode 100644 docker/simple/pulp-primary/containers/.gitkeep delete mode 100644 docker/simple/pulp-secondary/containers/.gitkeep create mode 100644 docker/simple/settings/settings-primary.py create mode 100644 docker/simple/settings/settings-secondary.py diff --git a/.devcontainer/test_config.ini b/.devcontainer/test_config.ini index f82c2c2..a96c2ac 100644 --- a/.devcontainer/test_config.ini +++ b/.devcontainer/test_config.ini @@ -14,6 +14,7 @@ default_domain=pulpproject.com jwt_algorithm=HS256 jwt_token_lifetime_mins=480 admin_group=pulpmaster-rw +require_jwt_auth=false [pulp] deb_signing_service=pulp_deb @@ -26,6 +27,7 @@ internal_package_prefix=int_ package_name_replacement_pattern= package_name_replacement_rule= remote_tls_validation=true +use_https_for_sync=false [redis] host=redis-manager diff --git a/README.md b/README.md index 2a529b2..62e99f7 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,7 @@ default_domain=example.com jwt_algorithm=HS256 jwt_token_lifetime_mins=480 admin_group=pulpmaster-rw +require_jwt_auth=true [pulp] deb_signing_service=pulp_deb @@ -222,6 +223,9 @@ Defines authentication allowed against the API - `jwt_token_lifetime_mins`: Number of minutes JWT is valid for - `admin_group`: Directory group user must be a member of to carry out priveldged actions agains the API +- `require_jwt_auth`: Boolean whether to require JWT authentication for + protected API endpoints. Set to false for local development environments + where authentication is not needed. Defaults to true ### pulp diff --git a/docker/simple-cluster.yml b/docker/simple-cluster.yml index 6e27c23..cb484ea 100644 --- a/docker/simple-cluster.yml +++ b/docker/simple-cluster.yml @@ -10,7 +10,8 @@ services: - "./simple/pulp-primary/storage:/var/lib/pulp" - "./simple/pulp-primary/pgsql:/var/lib/pgsql" - "./simple/pulp-primary/containers:/var/lib/containers" - - "./simple/settings:/etc/pulp:z" + - "./simple/settings/settings-primary.py:/etc/pulp/settings.py:z" + - "../assets/certs/database_fields.symmetric.key:/etc/pulp/database_fields.symmetric.key:z" - "../assets/scripts:/opt/scripts:z" - "../assets/keys/gpg:/opt/gpg:z" devices: @@ -29,7 +30,8 @@ services: - "./simple/pulp-secondary/storage:/var/lib/pulp" - "./simple/pulp-secondary/pgsql:/var/lib/pgsql" - "./simple/pulp-secondary/containers:/var/lib/containers" - - "./simple/settings:/etc/pulp:z" + - "./simple/settings/settings-secondary.py:/etc/pulp/settings.py:z" + - "../assets/certs/database_fields.symmetric.key:/etc/pulp/database_fields.symmetric.key:z" - "../assets/scripts:/opt/scripts:z" - "../assets/keys/gpg:/opt/gpg:z" devices: diff --git a/docker/simple/config.ini b/docker/simple/config.ini index 519e994..6e7a102 100644 --- a/docker/simple/config.ini +++ b/docker/simple/config.ini @@ -14,6 +14,8 @@ default_domain=pulpproject.com jwt_algorithm=HS256 jwt_token_lifetime_mins=480 admin_group=pulpmaster-rw +# Disable JWT auth requirement for demo environment +require_jwt_auth=false [pulp] deb_signing_service=deb_signing_service diff --git a/docker/simple/pulp-primary/containers/.gitkeep b/docker/simple/pulp-primary/containers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/docker/simple/pulp-secondary/containers/.gitkeep b/docker/simple/pulp-secondary/containers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/docker/simple/settings/settings-primary.py b/docker/simple/settings/settings-primary.py new file mode 100644 index 0000000..2d5ede7 --- /dev/null +++ b/docker/simple/settings/settings-primary.py @@ -0,0 +1,68 @@ +CONTENT_ORIGIN = "http://pulp-primary" + +# Database settings +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'pulp', + 'USER': 'pulp', + 'PASSWORD': 'password', + 'HOST': '127.0.0.1', + 'PORT': '5432', + } +} + +# Database encryption key +DB_ENCRYPTION_KEY = "/etc/pulp/database_fields.symmetric.key" + +# Cache settings for performance +CACHE_ENABLED = True + +# Logging +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "simple": {"format": "pulp: %(name)s:%(levelname)s: %(message)s"} + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "simple" + } + }, + "loggers": { + "": { + "handlers": ["console"], + "level": "INFO" + } + } +} + +# Task settings +RQ_QUEUES = { + 'default': { + 'HOST': 'localhost', + 'PORT': 6379, + 'DB': 0, + 'DEFAULT_TIMEOUT': 360, + }, +} + +# Enable package signing +PACKAGE_SIGNING_SERVICE = True + +# Token authentication +TOKEN_AUTH_DISABLED = False + +# Content settings +MEDIA_ROOT = "/var/lib/pulp/" + +# Default admin user +PULP_DEFAULT_ADMIN_PASSWORD = "password" +# Signing service configuration +SIGNING_SERVICES = { + 'deb_signing_service': { + 'SCRIPT': '/opt/scripts/deb_sign.sh', + } +} diff --git a/docker/simple/settings/settings-secondary.py b/docker/simple/settings/settings-secondary.py new file mode 100644 index 0000000..845b7d8 --- /dev/null +++ b/docker/simple/settings/settings-secondary.py @@ -0,0 +1,68 @@ +CONTENT_ORIGIN = "http://pulp-secondary" + +# Database settings +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'pulp', + 'USER': 'pulp', + 'PASSWORD': 'password', + 'HOST': '127.0.0.1', + 'PORT': '5432', + } +} + +# Database encryption key +DB_ENCRYPTION_KEY = "/etc/pulp/database_fields.symmetric.key" + +# Cache settings for performance +CACHE_ENABLED = True + +# Logging +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "simple": {"format": "pulp: %(name)s:%(levelname)s: %(message)s"} + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "simple" + } + }, + "loggers": { + "": { + "handlers": ["console"], + "level": "INFO" + } + } +} + +# Task settings +RQ_QUEUES = { + 'default': { + 'HOST': 'localhost', + 'PORT': 6379, + 'DB': 0, + 'DEFAULT_TIMEOUT': 360, + }, +} + +# Enable package signing +PACKAGE_SIGNING_SERVICE = True + +# Token authentication +TOKEN_AUTH_DISABLED = False + +# Content settings +MEDIA_ROOT = "/var/lib/pulp/" + +# Default admin user +PULP_DEFAULT_ADMIN_PASSWORD = "password" +# Signing service configuration +SIGNING_SERVICES = { + 'deb_signing_service': { + 'SCRIPT': '/opt/scripts/deb_sign.sh', + } +} diff --git a/pulp_manager/app/repositories/table_repository.py b/pulp_manager/app/repositories/table_repository.py index dfa3333..1488e3f 100644 --- a/pulp_manager/app/repositories/table_repository.py +++ b/pulp_manager/app/repositories/table_repository.py @@ -659,18 +659,11 @@ def bulk_add(self, entities: List): :type entities: list :return: list """ - - # Create model instances from dictionaries - new_entities = [] - for entity_dict in entities: - new_entity = self.__model__(**entity_dict) - self.db.add(new_entity) - new_entities.append(new_entity) - - # Flush to get the IDs assigned - self.db.flush() - - return new_entities + + result = self.db.scalars( + insert(self.__model__).returning(self.__model__), entities + ) + return result.all() def update(self, entity, **kwargs): """Updates existing entity in db but does not commit diff --git a/pulp_manager/app/routers/v1/pulp_servers.py b/pulp_manager/app/routers/v1/pulp_servers.py index 70a1bd0..0624ad9 100644 --- a/pulp_manager/app/routers/v1/pulp_servers.py +++ b/pulp_manager/app/routers/v1/pulp_servers.py @@ -33,6 +33,19 @@ from pulp_manager.app.services import PulpManager +# Helper function to conditionally apply JWT authentication +def get_jwt_dependencies(): + """Returns JWT dependencies if required by configuration, otherwise empty list""" + if "auth" in CONFIG and "require_jwt_auth" in CONFIG["auth"]: + require_jwt = CONFIG["auth"]["require_jwt_auth"] + if isinstance(require_jwt, str): + require_jwt = require_jwt.lower() == "true" + if not require_jwt: + return [] + # Default to requiring JWT auth (backward compatible) + return [Depends(JWTBearer(allowed_groups=CONFIG["auth"]["admin_group"].split(",")))] + + pulp_server_v1_router = APIRouter( prefix="/v1/pulp_servers", tags=["pulp_servers"], @@ -329,9 +342,7 @@ def get_repo_groups_by_group_id( name="pulp_servers_v1:snapshot_repos", response_model=Task, status_code=201, - dependencies=[ - Depends(JWTBearer(allowed_groups=CONFIG["auth"]["admin_group"].split(","))) - ], + dependencies=get_jwt_dependencies(), ) def snapshot_repos( id: int, snapshot_config: PulpServerSnapshotConfig, db: get_session = Depends() @@ -357,6 +368,7 @@ def snapshot_repos( name="pulp_servers_v1:sync_repos", response_model=Task, status_code=201, + dependencies=get_jwt_dependencies(), ) def sync_repos(id: int, sync_config: PulpServerSyncConfig, db: get_session = Depends()): """Queues a repo sync job against the specified pulp server""" @@ -382,9 +394,7 @@ def sync_repos(id: int, sync_config: PulpServerSyncConfig, db: get_session = Dep name="pulp_servers_v1:remove_repos", response_model=Task, status_code=201, - dependencies=[ - Depends(JWTBearer(allowed_groups=CONFIG["auth"]["admin_group"].split(","))) - ], + dependencies=get_jwt_dependencies(), ) def remove_repos( id: int, removal_config: PulpServerRepoRemovalConfig, db: get_session = Depends() diff --git a/pulp_manager/app/services/pulp_manager.py b/pulp_manager/app/services/pulp_manager.py index 91b4c90..7cca788 100644 --- a/pulp_manager/app/services/pulp_manager.py +++ b/pulp_manager/app/services/pulp_manager.py @@ -776,7 +776,7 @@ def create_or_update_repository( :return: PulpServerRepo """ - if description is None or "base_url" not in description: + if "base_url" not in description: raise PulpManagerValueError( f"Could not determine base_url for {name} from description" ) @@ -1165,7 +1165,7 @@ def _create_or_update_repository_source_pulp_server( if isinstance(source_repo, DebRepository): distributions = self._get_apt_distributions_from_url( - url + source_distribution.base_url ) if len(distributions) == 0: # raise PulpManagerValueError( @@ -1179,14 +1179,9 @@ def _create_or_update_repository_source_pulp_server( log.debug(f"create/update repo source {source_repo.name} URL {url}") - # Create description string in the format expected by create_or_update_repository - description_str = f"base_url: {url}" - if source_repo.description: - description_str += f"\ndescription: {source_repo.description}" - self.create_or_update_repository( name=source_repo.name, - description=description_str, + description=source_repo.description, repo_type=get_repo_type_from_href(source_repo.pulp_href), url=url, distributions=" ".join(distributions) if distributions else None, diff --git a/pulp_manager/tests/unit/services/test_repo_syncher.py b/pulp_manager/tests/unit/services/test_repo_syncher.py index dad99e1..67cd293 100644 --- a/pulp_manager/tests/unit/services/test_repo_syncher.py +++ b/pulp_manager/tests/unit/services/test_repo_syncher.py @@ -439,8 +439,8 @@ def test_start_remove_banned_packages_skip1(self, mock_find_packages_to_remove, result = self.repo_syncher._start_remove_banned_packages(task) assert result == False - @patch("pulp_manager.app.services.repo_syncher.get_repo", autospec=True) - @patch("pulp_manager.app.services.repo_syncher.get_remote", autospec=True) + @patch("pulp_manager.app.services.repo_syncher.get_repo") + @patch("pulp_manager.app.services.repo_syncher.get_remote") def test_start_remove_banned_packages_skip2(self, mock_get_remote, mock_get_repo): """Tests that if the feed was from an internal domain then no checks are done for banned packages @@ -449,14 +449,13 @@ def test_start_remove_banned_packages_skip2(self, mock_get_remote, mock_get_repo mock_get_repo.return_value = RpmRepository(**{ "pulp_href": "/pulp/api/v3/repositories/rpm/rpm/123", "name": "test-rpm", - "latest_version_href": "/pulp/api/v3/repositories/rpm/rpm/123/versions/1", - "remote": "/pulp/api/v3/remotes/rpm/rpm/123" + "latest_version_href": "/pulp/api/v3/repositories/rpm/rpm/123/versions/1" }) mock_get_remote.return_value = RpmRemote(**{ "pulp_href": "/pulp/api/v3/remotes/rpm/rpm/123", "name": "test-rpm", - "url": "https://pulp-primary/pulp/content/test-repo/", + "url": "https://pulp.example.com/", "policy": "immediate" }) diff --git a/setup-demo.sh b/setup-demo.sh index 24a2d5b..ea4ad30 100755 --- a/setup-demo.sh +++ b/setup-demo.sh @@ -98,16 +98,30 @@ setup_repo_and_remote() { echo " Setting up $repo_name on $(basename $server)..." - # Create repository + # Create repository with proper description format for Pulp Manager + # The base_url in description should be just the repo name (used for base_path) + # Not the full remote URL + local base_url="$repo_name" + local full_description="base_url: $base_url" + if [ -n "$repo_desc" ]; then + full_description="$full_description\ndescription: $repo_desc" + fi + if check_exists "$server/pulp/api/v3/repositories/deb/apt/" "$repo_name"; then echo " Repository '$repo_name' already exists" REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$server/pulp/api/v3/repositories/deb/apt/?name=$repo_name") REPO_HREF=$(echo "$REPO_RESPONSE" | jq -r '.results[0].pulp_href') + + # Update the description to have proper format + echo " Updating repository description..." + curl -s -u $PULP_USER:$PULP_PASS -X PATCH "$server$REPO_HREF" \ + -H "Content-Type: application/json" \ + -d "{\"description\": \"$full_description\"}" > /dev/null else echo " Creating repository '$repo_name'..." REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$server/pulp/api/v3/repositories/deb/apt/" \ -H "Content-Type: application/json" \ - -d "{\"name\": \"$repo_name\", \"description\": \"$repo_desc\"}") + -d "{\"name\": \"$repo_name\", \"description\": \"$full_description\"}") REPO_HREF=$(echo "$REPO_RESPONSE" | jq -r '.pulp_href') echo " Repository created: $REPO_HREF" fi @@ -190,6 +204,23 @@ check_server_running "$PULP_PRIMARY" "Pulp Primary" check_server_running "$PULP_SECONDARY" "Pulp Secondary" # Function to register signing service in Pulp +assign_signing_service_to_repo() { + local server="$1" + local repo_href="$2" + local repo_name="$3" + + # Get the signing service href for this server + local signing_service_href=$(curl -s -u $PULP_USER:$PULP_PASS "$server/pulp/api/v3/signing-services/?name=deb_signing_service" | jq -r '.results[0].pulp_href') + + if [ -n "$signing_service_href" ] && [ "$signing_service_href" != "null" ]; then + echo " Assigning signing service to $repo_name..." + curl -s -u $PULP_USER:$PULP_PASS -X PATCH "$server$repo_href" \ + -H "Content-Type: application/json" \ + -d "{\"signing_service\": \"$signing_service_href\"}" > /dev/null + echo " Signing service assigned: $signing_service_href" + fi +} + register_signing_service() { local container_name="$1" local server_name="$2" @@ -240,6 +271,8 @@ EXT_REPO_HREF=$REPO_HREF # Setup internal repo on primary (no remote needed) setup_repo_and_remote "$PULP_PRIMARY" "int-demo-packages" "Internal demo repository" "" "" "" "" "" INT_REPO_HREF=$REPO_HREF +# Assign signing service to internal repo on primary +assign_signing_service_to_repo "$PULP_PRIMARY" "$INT_REPO_HREF" "int-demo-packages" echo "" echo "Step 2: Setting up Secondary Server Repositories" @@ -248,6 +281,9 @@ echo "===============================================" # Setup repos on secondary that sync from primary setup_repo_and_remote "$PULP_SECONDARY" "int-demo-packages" "Internal demo packages synced from primary" \ "int-demo-remote" "http://docker-pulp-primary-1/pulp/content/int-demo-packages/" "stable" "" "" +SECONDARY_INT_REPO_HREF=$REPO_HREF +# Assign signing service to internal repo on secondary +assign_signing_service_to_repo "$PULP_SECONDARY" "$SECONDARY_INT_REPO_HREF" "int-demo-packages" setup_repo_and_remote "$PULP_SECONDARY" "ext-small-repo" "External small repo synced from primary" \ "ext-small-remote" "http://docker-pulp-primary-1/pulp/content/ext-small-repo/" "testing" "main" "amd64" From 1d7037b300d2f9e20203c9934590948309b117dd Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Wed, 1 Oct 2025 15:06:42 -0400 Subject: [PATCH 13/24] Readme tweaks Signed-off-by: Geoff Wilson --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 62e99f7..697903d 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,8 @@ or Jenkins. ## Core Team -This project is maintained by G-Research. For details on our team and +This project originated at [G-Research](https://github.com/G-Research) +but is now owned by the Pulp project. For details on our team and roles, please see the [MAINTAINERS.md](MAINTAINERS.md) file. ## Documentation Index @@ -128,14 +129,14 @@ components: ## Quick Start -1. **Using DevContainers (Recommended)** +1. **For Development, use Dev Container** ```bash # Open in VS Code and select action "Dev Containers: Reopen in Container" # Or use the Dev Container CLI: devcontainer up --workspace-folder . ``` -2. **Manual Setup** +2. **For Demo cluster, use make targets to setup a Docker Compose environment** ```bash make run-cluster make setup-cluster From 614427e84996c6eb8af2f7f63b4cdd1bfb253f5f Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Wed, 1 Oct 2025 22:15:51 -0400 Subject: [PATCH 14/24] Fixes for deb_signing and processing inserts with dictionaries Signed-off-by: Geoff Wilson --- docker/simple/config.ini | 5 ++++- pulp_manager/app/repositories/table_repository.py | 15 +++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/docker/simple/config.ini b/docker/simple/config.ini index 6e7a102..4245e80 100644 --- a/docker/simple/config.ini +++ b/docker/simple/config.ini @@ -18,7 +18,10 @@ admin_group=pulpmaster-rw require_jwt_auth=false [pulp] -deb_signing_service=deb_signing_service +# Signing services exist on servers and are registered via setup-demo.sh +# But we don't set deb_signing_service here to avoid Pulp Manager auto-applying it +# during syncs (each server has different hrefs for their signing services) +# deb_signing_service=deb_signing_service banned_package_regex=bannedexample|another internal_domains=pulp-primary,pulp-secondary git_repo_config_dir=repo_config diff --git a/pulp_manager/app/repositories/table_repository.py b/pulp_manager/app/repositories/table_repository.py index 1488e3f..aab373f 100644 --- a/pulp_manager/app/repositories/table_repository.py +++ b/pulp_manager/app/repositories/table_repository.py @@ -660,10 +660,17 @@ def bulk_add(self, entities: List): :return: list """ - result = self.db.scalars( - insert(self.__model__).returning(self.__model__), entities - ) - return result.all() + # Create model instances from dictionaries + new_entities = [] + for entity_dict in entities: + new_entity = self.__model__(**entity_dict) + self.db.add(new_entity) + new_entities.append(new_entity) + + # Flush to get the IDs assigned + self.db.flush() + + return new_entities def update(self, entity, **kwargs): """Updates existing entity in db but does not commit From 1a92aa24c4e66d1586494d0932cc86d851df56c1 Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Thu, 2 Oct 2025 13:30:11 -0400 Subject: [PATCH 15/24] test fixes to match shared config Signed-off-by: Geoff Wilson --- pulp_manager/tests/unit/services/test_pulp_manager.py | 2 +- pulp_manager/tests/unit/services/test_repo_syncher.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pulp_manager/tests/unit/services/test_pulp_manager.py b/pulp_manager/tests/unit/services/test_pulp_manager.py index ff7dca3..c9feece 100644 --- a/pulp_manager/tests/unit/services/test_pulp_manager.py +++ b/pulp_manager/tests/unit/services/test_pulp_manager.py @@ -607,7 +607,7 @@ def test_generate_feed_from_distribution(self): "base_path": "el7-x86_64/ext-rpm" }) - expected = "https://pulp_server.domain.local/pulp/content/el7-x86_64/ext-rpm" + expected = "http://pulp_server.domain.local/pulp/content/el7-x86_64/ext-rpm" seen = self.pulp_manager._generate_feed_from_distribution("pulp_server.domain.local", distribution) assert seen == expected diff --git a/pulp_manager/tests/unit/services/test_repo_syncher.py b/pulp_manager/tests/unit/services/test_repo_syncher.py index 67cd293..39671d6 100644 --- a/pulp_manager/tests/unit/services/test_repo_syncher.py +++ b/pulp_manager/tests/unit/services/test_repo_syncher.py @@ -455,7 +455,7 @@ def test_start_remove_banned_packages_skip2(self, mock_get_remote, mock_get_repo mock_get_remote.return_value = RpmRemote(**{ "pulp_href": "/pulp/api/v3/remotes/rpm/rpm/123", "name": "test-rpm", - "url": "https://pulp.example.com/", + "url": "https://pulp-primary/", "policy": "immediate" }) From a46811724c783bc302f41b570884dd00bd187808 Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Fri, 3 Oct 2025 10:28:22 -0400 Subject: [PATCH 16/24] Move docker/simple/ to /demo -- including files in /assets Signed-off-by: Geoff Wilson --- .devcontainer/docker-compose.yml | 4 +- Makefile | 6 +- assets/bin/nginx.sh | 32 ++ assets/nginx/nginx.conf.template | 89 ++++++ assets/settings.py | 19 ++ {assets => demo/assets}/certs/.gitkeep | 0 .../certs}/database_fields.symmetric.key | 0 demo/assets/certs/pulp_webserver.crt | 23 ++ demo/assets/certs/pulp_webserver.csr | 20 ++ demo/assets/certs/pulp_webserver.key | 28 ++ demo/assets/certs/token_private_key.pem | 5 + demo/assets/certs/token_public_key.pem | 4 + {assets => demo/assets}/keys/.gitkeep | 0 .../keys/container_auth_private_key.pem | 5 + .../assets/keys/container_auth_public_key.pem | 4 + ...6957F72E4E766D9F0DA17C7176329BF3E5467B.rev | 32 ++ ...4746844C0C150A7CD566D9784172C3BEC0ED90.key | 28 ++ demo/assets/keys/gpg/public.key | 18 ++ demo/assets/keys/gpg/pubring.kbx | Bin 0 -> 843 bytes demo/assets/keys/gpg/pubring.kbx~ | Bin 0 -> 32 bytes demo/assets/keys/gpg/trustdb.gpg | Bin 0 -> 1280 bytes .../assets}/packages/hello_2.10-2_amd64.deb | Bin {assets => demo/assets}/scripts/deb_sign.sh | 0 .../assets}/scripts/signing_service.py | 0 {docker/simple => demo}/config.ini | 0 .../docker-compose.yml | 42 ++- {docker/simple => demo}/pulp-config.yml | 0 .../certs/database_fields.symmetric.key | 1 + demo/settings/certs/pulp_webserver.crt | 23 ++ demo/settings/certs/pulp_webserver.csr | 20 ++ demo/settings/certs/pulp_webserver.key | 28 ++ demo/settings/certs/token_private_key.pem | 5 + demo/settings/certs/token_public_key.pem | 4 + demo/settings/database_fields.symmetric.key | 1 + .../settings/settings-primary.py | 0 .../settings/settings-secondary.py | 0 {docker/simple => demo}/settings/settings.py | 0 pytest.ini | 2 +- setup-demo.sh | 299 ++++-------------- 39 files changed, 471 insertions(+), 271 deletions(-) create mode 100755 assets/bin/nginx.sh create mode 100644 assets/nginx/nginx.conf.template create mode 100644 assets/settings.py rename {assets => demo/assets}/certs/.gitkeep (100%) rename {docker/simple/settings => demo/assets/certs}/database_fields.symmetric.key (100%) create mode 100644 demo/assets/certs/pulp_webserver.crt create mode 100644 demo/assets/certs/pulp_webserver.csr create mode 100644 demo/assets/certs/pulp_webserver.key create mode 100644 demo/assets/certs/token_private_key.pem create mode 100644 demo/assets/certs/token_public_key.pem rename {assets => demo/assets}/keys/.gitkeep (100%) create mode 100644 demo/assets/keys/container_auth_private_key.pem create mode 100644 demo/assets/keys/container_auth_public_key.pem create mode 100644 demo/assets/keys/gpg/openpgp-revocs.d/086957F72E4E766D9F0DA17C7176329BF3E5467B.rev create mode 100644 demo/assets/keys/gpg/private-keys-v1.d/AB4746844C0C150A7CD566D9784172C3BEC0ED90.key create mode 100644 demo/assets/keys/gpg/public.key create mode 100644 demo/assets/keys/gpg/pubring.kbx create mode 100644 demo/assets/keys/gpg/pubring.kbx~ create mode 100644 demo/assets/keys/gpg/trustdb.gpg rename {assets => demo/assets}/packages/hello_2.10-2_amd64.deb (100%) rename {assets => demo/assets}/scripts/deb_sign.sh (100%) rename {assets => demo/assets}/scripts/signing_service.py (100%) rename {docker/simple => demo}/config.ini (100%) rename docker/simple-cluster.yml => demo/docker-compose.yml (66%) rename {docker/simple => demo}/pulp-config.yml (100%) create mode 100644 demo/settings/certs/database_fields.symmetric.key create mode 100644 demo/settings/certs/pulp_webserver.crt create mode 100644 demo/settings/certs/pulp_webserver.csr create mode 100644 demo/settings/certs/pulp_webserver.key create mode 100644 demo/settings/certs/token_private_key.pem create mode 100644 demo/settings/certs/token_public_key.pem create mode 100644 demo/settings/database_fields.symmetric.key rename {docker/simple => demo}/settings/settings-primary.py (100%) rename {docker/simple => demo}/settings/settings-secondary.py (100%) rename {docker/simple => demo}/settings/settings.py (100%) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 0bee1e6..e49a5e4 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -15,8 +15,8 @@ services: DB_USER: pulp-manager DB_PASSWORD: pulp-manager JWT_SECRET: test_secret - PULP_MANAGER_CONFIG_PATH: /workspace/docker/simple/config.ini - PULP_SYNC_CONFIG_PATH: /workspace/docker/simple/pulp_config.yml + PULP_MANAGER_CONFIG_PATH: /workspace/demo/config.ini + PULP_SYNC_CONFIG_PATH: /workspace/demo/pulp-config.yml Is_local: "true" networks: - pulp-devcontainer-net diff --git a/Makefile b/Makefile index b0385e7..9049dbe 100644 --- a/Makefile +++ b/Makefile @@ -58,17 +58,17 @@ venv: requirements.txt run-pulp-manager: setup-network setup-keys @echo "Starting Pulp Manager services..." - docker compose -f docker/simple-cluster.yml up mariadb redis-manager pulp-manager-api pulp-manager-worker pulp-manager-scheduler --build + docker compose -f demo/docker-compose.yml up mariadb redis-manager pulp-manager-api pulp-manager-worker pulp-manager-scheduler --build .PHONY : run-pulp3 run-pulp3: setup-network @echo "Starting simplified Pulp 3 primary and secondary..." - docker compose -f docker/simple-cluster.yml up pulp-primary pulp-secondary --build + docker compose -f demo/docker-compose.yml up pulp-primary pulp-secondary --build .PHONY : run-cluster run-cluster: setup-network setup-keys @echo "Starting complete simplified cluster with Pulp Manager, Primary and Secondary Pulp instances..." - docker compose -f docker/simple-cluster.yml up --build + docker compose -f demo/docker-compose.yml up --build setup-network: @echo "Creating or verifying network..." diff --git a/assets/bin/nginx.sh b/assets/bin/nginx.sh new file mode 100755 index 0000000..d9bfdd1 --- /dev/null +++ b/assets/bin/nginx.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# This logic enables us to have multiple servers, and check to see +# if they are scaled every 10 seconds. +# https://serverfault.com/a/821625/189494 +# https://www.nginx.com/blog/dns-service-discovery-nginx-plus#domain-name-variable + +set -e + +if [ "$container" == "podman" ]; then + # the nameserver list under podman is unreliable. + # It will look like "10.89.1.1 192.168.1.1 192.168.1.1", but only the 1st IP works. + # This doesn't mess up `nslookup`, but it messes up `getent hosts` and nginx. + export NAMESERVER=`cat /etc/resolv.conf | grep "nameserver" | awk '{print $2}' | head -n1` +else + export NAMESERVER=`cat /etc/resolv.conf | grep "nameserver" | awk '{print $2}' | tr '\n' ' '` +fi + +echo "Nameserver is: $NAMESERVER" + +echo "Generating nginx config" +envsubst '$NAMESERVER' < ../nginx/nginx.conf.template > /etc/nginx/nginx.conf + +# We cannot use upstream server groups with a DNS resolver without nginx plus +# So we modifying the files to use the variables rather than the upstream server groups +for file in /etc/nginx/conf.d/*.conf ; do + echo "Modifying $file" + sed -i 's/pulp-api/$pulp_api:24817/' $file + sed -i 's/pulp-content/$pulp_content:24816/' $file +done + +echo "Starting nginx" +exec nginx -g "daemon off;" \ No newline at end of file diff --git a/assets/nginx/nginx.conf.template b/assets/nginx/nginx.conf.template new file mode 100644 index 0000000..3707d09 --- /dev/null +++ b/assets/nginx/nginx.conf.template @@ -0,0 +1,89 @@ +error_log /dev/stdout info; +worker_processes 1; +events { + worker_connections 1024; # increase if you have lots of clients + accept_mutex off; # set to 'on' if nginx worker_processes > 1 +} + +http { + access_log /dev/stdout; + include mime.types; + # fallback in case we can't determine a type + default_type application/octet-stream; + sendfile on; + + # If left at the default of 1024, nginx emits a warning about being unable + # to build optimal hash types. + types_hash_max_size 4096; + + server { + # This logic enables us to have multiple servers, and check to see + # if they are scaled every 10 seconds. + # https://www.nginx.com/blog/dns-service-discovery-nginx-plus#domain-name-variable + # https://serverfault.com/a/821625/189494 + resolver $NAMESERVER valid=10s; + set $pulp_api pulp_api; + set $pulp_content pulp_content; + + # Gunicorn docs suggest the use of the "deferred" directive on Linux. + listen 8080 default_server deferred; + listen [::]:8080 default_server deferred; + + # If you have a domain name, this is where to add it + server_name $hostname; + + # The default client_max_body_size is 1m. Clients uploading + # files larger than this will need to chunk said files. + client_max_body_size 10m; + + # Gunicorn docs suggest this value. + keepalive_timeout 5; + + # static files that can change dynamically, or are needed for TLS + # purposes are served through the webserver. + root /opt/app-root/src; + + location /pulp/content/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_content:24816; + } + + location /pulp/api/v3/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + location /auth/login/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + } + + include /opt/app-root/etc/nginx.default.d/*.conf; + + location / { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://$pulp_api:24817; + # static files are served through whitenoise - http://whitenoise.evans.io/en/stable/ + } + } +} \ No newline at end of file diff --git a/assets/settings.py b/assets/settings.py new file mode 100644 index 0000000..04b5590 --- /dev/null +++ b/assets/settings.py @@ -0,0 +1,19 @@ +SECRET_KEY = "aabbcc" +CONTENT_ORIGIN = "http://pulp_content:24816" +DATABASES = {"default": {"HOST": "postgres", "ENGINE": "django.db.backends.postgresql", "NAME": "pulp", "USER": "pulp", "PASSWORD": "password", "PORT": "5432", "CONN_MAX_AGE": 0, "OPTIONS": {"sslmode": "prefer"}}} +CACHE_ENABLED = True +REDIS_HOST = "redis" +REDIS_PORT = 6379 +REDIS_PASSWORD = "" +ANSIBLE_API_HOSTNAME = "http://pulp_api:24817" +ANSIBLE_CONTENT_HOSTNAME = "http://pulp_content:24816/pulp/content" +ALLOWED_IMPORT_PATHS = ["/tmp"] +ALLOWED_EXPORT_PATHS = ["/tmp"] +TOKEN_SERVER = "http://pulp_api:24817/token/" +TOKEN_AUTH_DISABLED = False +TOKEN_SIGNATURE_ALGORITHM = "ES256" +PUBLIC_KEY_PATH = "/etc/pulp/keys/container_auth_public_key.pem" +PRIVATE_KEY_PATH = "/etc/pulp/keys/container_auth_private_key.pem" +ANALYTICS = False +STATIC_ROOT = "/var/lib/operator/static/" +ALLOWED_HOSTS = ['pulp-web:8080', 'localhost', '127.0.0.1', '[::1]'] \ No newline at end of file diff --git a/assets/certs/.gitkeep b/demo/assets/certs/.gitkeep similarity index 100% rename from assets/certs/.gitkeep rename to demo/assets/certs/.gitkeep diff --git a/docker/simple/settings/database_fields.symmetric.key b/demo/assets/certs/database_fields.symmetric.key similarity index 100% rename from docker/simple/settings/database_fields.symmetric.key rename to demo/assets/certs/database_fields.symmetric.key diff --git a/demo/assets/certs/pulp_webserver.crt b/demo/assets/certs/pulp_webserver.crt new file mode 100644 index 0000000..4c36051 --- /dev/null +++ b/demo/assets/certs/pulp_webserver.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDzDCCArSgAwIBAgIURAIunfFsxKyouqd597QLlcbTIcowDQYJKoZIhvcNAQEL +BQAwYTENMAsGA1UEAwwEcHVscDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5DMRAw +DgYDVQQHDAdSYWxlaWdoMRYwFAYDVQQKDA1SZWQgSGF0LCBJbmMuMQwwCgYDVQQL +DANwbnQwHhcNMjUwOTI5MTc0MDI1WhcNMjYwOTI5MTc0MDI1WjBhMQ0wCwYDVQQD +DARwdWxwMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxEDAOBgNVBAcMB1JhbGVp +Z2gxFjAUBgNVBAoMDVJlZCBIYXQsIEluYy4xDDAKBgNVBAsMA3BudDCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAN1Ak3WZvs8aJPpTkp78Iq5d8N0H5MDh +WdkNX7GFxti7SJJZ4IkwjuCzMXOSNMWBxCcZCUCsrl36WgjNDxOC6ljBq7zj9eIZ +U3V+BsohuStgwBb4Jrw7FRVZTLEn4JIuv56wQGh6356p/ABgYQChF8aYLjowZ+sP +WtUd29osaRmc01h62vOUvHztyGYliKUPQKUjKxjhudqBQ5LE6ZMX7Unr7vpL1Zkw +Z2WIzvdneesF6GKYjs5262+PSl5aOg0Nbl1SHUK8UagkAlEcq3BhJHpEZJIsanCx +bcoBf5i6iS4Z/19MscfcWKXtTLtb45u5uPq+Abhpxl3j55ElwwQZiTUCAwEAAaN8 +MHowWQYDVR0RBFIwUIIEcHVscIIQcHVscC5leGFtcGxlLmNvbYILY2kucHVscC5j +b22CBmdhbGF4eYISZ2FsYXh5LmV4YW1wbGUuY29tgg1jaS5nYWxheHkuY29tMB0G +A1UdDgQWBBTY1ep9PgMxX1j0G3vEo8dtOCGLFDANBgkqhkiG9w0BAQsFAAOCAQEA +lC3wrjKbD00hQHCy0DTPgWCLo1RwmsB3ogL00A8uaGgfocXx7BPCCcUKLAKfpSzo ++Z8BpeqF0u8ZCatqeE9nDvytbEh6775L925OUv/UE9luE2nyh889QNzFhB2oAOM+ +hXK4An8P8uTMA24EhcYCoCxzvftZ6DdRqGsDSD6oJzUhQhiYR2OctgVFxYNopT7u +gJP38mI2tguiX+QdFsrudCMZzrmUdpbyG/60PNPnMYGYhqb6F0SdL6Yq0KY76qUH +HgBId3tGh4lZIfYbixg/IySRmGNHkLl9FHxbUpaUI27kIIoMjb7PWWbnPOo6Imqx +nbm7O6uq3ZNDypnmcxP5qg== +-----END CERTIFICATE----- diff --git a/demo/assets/certs/pulp_webserver.csr b/demo/assets/certs/pulp_webserver.csr new file mode 100644 index 0000000..fa747ae --- /dev/null +++ b/demo/assets/certs/pulp_webserver.csr @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIDPjCCAiYCAQAwYTENMAsGA1UEAwwEcHVscDELMAkGA1UEBhMCVVMxCzAJBgNV +BAgMAk5DMRAwDgYDVQQHDAdSYWxlaWdoMRYwFAYDVQQKDA1SZWQgSGF0LCBJbmMu +MQwwCgYDVQQLDANwbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDd +QJN1mb7PGiT6U5Ke/CKuXfDdB+TA4VnZDV+xhcbYu0iSWeCJMI7gszFzkjTFgcQn +GQlArK5d+loIzQ8TgupYwau84/XiGVN1fgbKIbkrYMAW+Ca8OxUVWUyxJ+CSLr+e +sEBoet+eqfwAYGEAoRfGmC46MGfrD1rVHdvaLGkZnNNYetrzlLx87chmJYilD0Cl +IysY4bnagUOSxOmTF+1J6+76S9WZMGdliM73Z3nrBehimI7Odutvj0peWjoNDW5d +Uh1CvFGoJAJRHKtwYSR6RGSSLGpwsW3KAX+YuokuGf9fTLHH3Fil7Uy7W+Obubj6 +vgG4acZd4+eRJcMEGYk1AgMBAAGggZcwgZQGCSqGSIb3DQEJDjGBhjCBgzAJBgNV +HRMEAjAAMAsGA1UdDwQEAwIF4DBpBgNVHREEYjBgggRwdWxwghBwdWxwLmV4YW1w +bGUuY29tggtjaS5wdWxwLmNvbYIGZ2FsYXh5ghJnYWxheHkuZXhhbXBsZS5jb22C +DWNpLmdhbGF4eS5jb22CDnB1bHAtZXBoZW1lcmFsMA0GCSqGSIb3DQEBCwUAA4IB +AQDISjZSZRXm1jdM3gZiajNJ3wqudlSezbPT0TXqdJLUnoJXkpIzmDma4faqJIit +4SMrvFH1TIeyTuWQDcEAGv+Eq0epsWrHR/FiqlKL1l0G1qk7DEbXQAbsE5mBauLX +JxT69pS5toD0zN9XfVm8rvzyNwigNurlbtrI7ycu+E3lQwxEjNrA+XR4rsbfzNx+ +/WyRmJUZBMS4432uibSW2MzXmaKaMLOiPmmTSpXwMW88BT2IxYirEk5kDedZ1LTA +1U6v0q8wKtIpzjmuqDjW2vq1q+GKpAmOS5faXIUUeipGuAaeKIf7rrmljFKE9VQe +ErRx0c/skNjVxTQo+T24Qep5 +-----END CERTIFICATE REQUEST----- diff --git a/demo/assets/certs/pulp_webserver.key b/demo/assets/certs/pulp_webserver.key new file mode 100644 index 0000000..411d8e6 --- /dev/null +++ b/demo/assets/certs/pulp_webserver.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDdQJN1mb7PGiT6 +U5Ke/CKuXfDdB+TA4VnZDV+xhcbYu0iSWeCJMI7gszFzkjTFgcQnGQlArK5d+loI +zQ8TgupYwau84/XiGVN1fgbKIbkrYMAW+Ca8OxUVWUyxJ+CSLr+esEBoet+eqfwA +YGEAoRfGmC46MGfrD1rVHdvaLGkZnNNYetrzlLx87chmJYilD0ClIysY4bnagUOS +xOmTF+1J6+76S9WZMGdliM73Z3nrBehimI7Odutvj0peWjoNDW5dUh1CvFGoJAJR +HKtwYSR6RGSSLGpwsW3KAX+YuokuGf9fTLHH3Fil7Uy7W+Obubj6vgG4acZd4+eR +JcMEGYk1AgMBAAECggEABqljVzrgFyHBI8Vg6IcMb0YWUr1iWpleaG3h5/kwbcWj +z35Dx7Wt05+pmUJ5csnvs0Kqd+dLH7rCO7oa3lpGfpIkPt15QYvEKsk24J6n0eHJ +ntdtaST5Q0mLSNk7xoMAU4GYitzKP198XjGIsilnixv5ZlifRGFTuY9612SXbIUx +LdXV6mazuTP+bxFiIYBeLOkG+9qtQanSrPcdyxXTSU1Ve9F8194yqHpQx37c19vY +PP2ffWnwcz9O8IbRW9J9uzAX6y8/H5hegROTOszoPX8UBytwebPDlWBLNkg46kBQ +qzC32sRIQUF080SLr1nm37VFZe7gwnzG1xYIkdc2kQKBgQD+XjIL89QHuGFV6B2U +s+fUj1m/ODlzF6PfgcVCKw9B0sp2CeHrQn7iyM7Xt0BFrV88JDiCQgrulOU1cqbl +uoKhYtKdPJ5jl/uwKkl3/kPkHJrj8rlEWQKo3l+v9Hc8W/v5aZK7F5zw+4qoUraB +uxRJiHVWIksqvWfS5bIPuGeqJQKBgQDeq/zATuiNFML7reUHQUxPFew0RNtgslAx +6zUzdjl3hbXaOISOFU0yZvRg0I/8dVkUHjgHOX9u5k6YGrqDGzJ5/Vewo3bbHCBk +58u74OPvhVoqizcgoBF17Qrq6JwDbS+UkhJv7Y/vbbTmajQQlFF3q5unkg/bouFL +7BvhwdjN0QKBgQCgyUX1TDEQmDnepZRdNMMsF1jxiEa4O484br0TsEg6oVWc+240 +2Zl/HNOyg4E7CfYS/ApEPB7Q5IlmGYzp1dVQ0jizb2fnKGDN4E0EblLX1EUMJZd6 +XpFR0Q7HGE5udu51n4hCfxCTO01QTMhUhL60JG+W/KJq58LDCrJdQYE1iQKBgFSt +Ho6a80BDNuqydDfQEw64DXzK+onJBUoWYcLSIIRdKoxzlaTaYOLb1+7BISAmF9vY +qgHFUbqAhj69W1PkEcvmFWSspNQp2//DTeyCVuuM0H8BNdOIS1uG6vHtxvZenQto +iO5bbrLkCzjcBjSP0nMppSWSG8mwJPDUNr4hEyshAoGBAMSO/r+uCsBaJnbAZw2o +YGDmYL9iuP59T8GaGrAScA0lFYr1OAxTRPdEdW2xJEWJ1wPcucMD8zXzbKJ+Zuvu +ct9JRVn2gxfLpXteUpfaj3u3MrTVP3F5i9x9HWT5a0qCwlkOdcE+PJhLpEYYtbaZ +thoi2K72DfZJGbEv7JkRBNDB +-----END PRIVATE KEY----- diff --git a/demo/assets/certs/token_private_key.pem b/demo/assets/certs/token_private_key.pem new file mode 100644 index 0000000..15ed860 --- /dev/null +++ b/demo/assets/certs/token_private_key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIKt5HoJotGmry4eug7eZZnl+glYHe66UnEpVJ2E3kKZqoAoGCCqGSM49 +AwEHoUQDQgAE59WySChJ+/C01aYCq3a+0bt+QmsjeU60adfCSpjFKVVgZXqqgWHi ++0PCM/+uBBPCVqlSxgpdm11XPUApEComZw== +-----END EC PRIVATE KEY----- diff --git a/demo/assets/certs/token_public_key.pem b/demo/assets/certs/token_public_key.pem new file mode 100644 index 0000000..9318e84 --- /dev/null +++ b/demo/assets/certs/token_public_key.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE59WySChJ+/C01aYCq3a+0bt+Qmsj +eU60adfCSpjFKVVgZXqqgWHi+0PCM/+uBBPCVqlSxgpdm11XPUApEComZw== +-----END PUBLIC KEY----- diff --git a/assets/keys/.gitkeep b/demo/assets/keys/.gitkeep similarity index 100% rename from assets/keys/.gitkeep rename to demo/assets/keys/.gitkeep diff --git a/demo/assets/keys/container_auth_private_key.pem b/demo/assets/keys/container_auth_private_key.pem new file mode 100644 index 0000000..a36d6ec --- /dev/null +++ b/demo/assets/keys/container_auth_private_key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEINq1lvEDM9pLdlCp8TisKOCKis4XrvWOTQj50oyIx01loAoGCCqGSM49 +AwEHoUQDQgAEWy5LFfvJ/3xV3r1qmZUOkhstQNuVAYYfPg3e8CWgIas0YCmCQE8L +WnvoHlsEjKlMHblSElN7xnA/2PFzut1QSQ== +-----END EC PRIVATE KEY----- diff --git a/demo/assets/keys/container_auth_public_key.pem b/demo/assets/keys/container_auth_public_key.pem new file mode 100644 index 0000000..50b65c9 --- /dev/null +++ b/demo/assets/keys/container_auth_public_key.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWy5LFfvJ/3xV3r1qmZUOkhstQNuV +AYYfPg3e8CWgIas0YCmCQE8LWnvoHlsEjKlMHblSElN7xnA/2PFzut1QSQ== +-----END PUBLIC KEY----- diff --git a/demo/assets/keys/gpg/openpgp-revocs.d/086957F72E4E766D9F0DA17C7176329BF3E5467B.rev b/demo/assets/keys/gpg/openpgp-revocs.d/086957F72E4E766D9F0DA17C7176329BF3E5467B.rev new file mode 100644 index 0000000..5ba4998 --- /dev/null +++ b/demo/assets/keys/gpg/openpgp-revocs.d/086957F72E4E766D9F0DA17C7176329BF3E5467B.rev @@ -0,0 +1,32 @@ +This is a revocation certificate for the OpenPGP key: + +pub rsa2048 2025-09-15 [SCEAR] + 086957F72E4E766D9F0DA17C7176329BF3E5467B +uid Demo Signing Service + +A revocation certificate is a kind of "kill switch" to publicly +declare that a key shall not anymore be used. It is not possible +to retract such a revocation certificate once it has been published. + +Use it to revoke this key in case of a compromise or loss of +the secret key. However, if the secret key is still accessible, +it is better to generate a new revocation certificate and give +a reason for the revocation. For details see the description of +of the gpg command "--generate-revocation" in the GnuPG manual. + +To avoid an accidental use of this file, a colon has been inserted +before the 5 dashes below. Remove this colon with a text editor +before importing and publishing this revocation certificate. + +:-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: This is a revocation certificate + +iQE2BCABCgAgFiEECGlX9y5Odm2fDaF8cXYym/PlRnsFAmjIPnQCHQAACgkQcXYy +m/PlRnsCvgf+MHRXigUXuCL9zmx76ZveNHqZ7epKjjtRNvOwnGxsGTS9D3+kKwYm +44ciAWlPxB1IUK9k3HmdD9o1kvEbbrjJ6vwuZl9lkkXZJHWIeczdoEbdmUSHpHDQ +9eq0pWtdRKRjy2Ol/QGo7CrBO2KdGoN2E/ghd6bz2/8VeUqStP42VdEhB7mWIJ1L +nxD5281lCkTS06r7wviOZgJcOLQ1qPqxHDGAPlR4NPuZNMRG+X9JKoGXJFDCQjgc +Iu+sepH5O3mw3TgMrj+t6IDO3bhXh4TnxOvbwDHFoAZqcug25d34LPaFeEynerrb +OEIuvy1zCZSw0/scAU0izbVi03XjQ/VGJA== +=8csS +-----END PGP PUBLIC KEY BLOCK----- diff --git a/demo/assets/keys/gpg/private-keys-v1.d/AB4746844C0C150A7CD566D9784172C3BEC0ED90.key b/demo/assets/keys/gpg/private-keys-v1.d/AB4746844C0C150A7CD566D9784172C3BEC0ED90.key new file mode 100644 index 0000000..7cd7b95 --- /dev/null +++ b/demo/assets/keys/gpg/private-keys-v1.d/AB4746844C0C150A7CD566D9784172C3BEC0ED90.key @@ -0,0 +1,28 @@ +Created: 20250915T162732 +Key: (private-key (rsa (n #00921B86FB1BFE6D65ACAA6D7B7A638FF319BAFE8224 + 34C379AB4479AFB9D7ADC1166FDF4347756D1601837A543F3EE131589C2E6E465DA720 + 4F5F08636F88F05FAA63A6A6DB5579231B59C64762B6A6ABAA1E0FDEAB33317F2AA94B + 558C60A8932613F4BAFE877D4CB62A63A26E46C179279F37479F96DD87E8926E4A425A + 322F91707C0F46BC19D677D9FE3726666A960CF2218AB217D732C6CBB03E46BF131876 + B1099DAB7F9AD46378B3A6B60A75EF2D2EEE18E777416A593B28D605344C290E4249E2 + 3D9874646953347D592EA8558D482B3866A78E2CDA8050B5DB47297CF195BE0B5F9322 + DA3EAF2686AC62A267F27EB5CA00C35E51212C4FF95A0EEC55#)(e #010001#)(d + #08F569B53CCF2E812AC397DD855E549A3EA0EE7939C88796B33F8A5291E7069A8901 + 3AC3C099CA7E19EF1603D38D82C10D1E00BED1F3B61D860E14632A2AE21367039FC705 + 5F00BAA0C568D8D3FA3F611EF35A5C5394FF48F3D92146ACDC66AC0043478EADF98516 + 4EA0002FACC5E74EC2075477AE84ACE61EE2215A8526278384DFA7B5A4CF99CD1933DB + AD5151EAD783AD9705E04645E13AC6012087B712A581D0D00398D3FBE6711D0907ABEF + C0AC4F74A07AE301DA85BB471476271CC7B2CF6430AEC447C73EDD8AECFA0278DD31F9 + 607FD99C3FA7227955E289A46960B24DD2D97BED1123D0940965F1CFCB5DF91728845E + 8BDA6009C9D577AF62480215#)(p #00C09B034311E2811DCC15C5749CB52D0D36F1C1 + 266D67744934C6D2A191B6C6C9BE5D8C79C349C8C7BD70F03D2716D626062D99CD4197 + 1F2CE671D92D19C37EB340C7FE2EA05CC3D5103048588AA139CDC43E1F762A3F473AF2 + 91CC5A3556831FEAB31C606B2B07A843379D2B1CC0D5FDAB85C0010AE0BF9A31CDF5BB + BC094967#)(q #00C232952C6CB923DB790118AD52E3C9247E0D7495913A381D77BF77 + E4B6F3611A429828362C7074F71D69C7C102E827914D0615855F13E709A19F665A11AF + DFFD0BBA6135B93161987FBF2BA523434A1C15BADA78602A93627903980028E904322E + D8C6184B68470FBC617C776ADCAB448953377FAF111AF7DE83FAA518D2BAE3#)(u + #04E4F151931458CD7B6A1437C320DBB25DF8E04D119065C1912664322FFF25C5FC3E + 4F4F56F448C4901D9AA24BC276AD7458D5CE35EBBB010613434BEEA950A5D00C8A4288 + 217E53C121C1C52AC3F550E5D79CE4F1E0D9005E4EE4030834FDC3D29A2FA24450FB31 + 5D054A2F50625C78E759A0469BE3D61E9EC297CF3B7A9550#))) diff --git a/demo/assets/keys/gpg/public.key b/demo/assets/keys/gpg/public.key new file mode 100644 index 0000000..b8683f7 --- /dev/null +++ b/demo/assets/keys/gpg/public.key @@ -0,0 +1,18 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBGjIPnQBCACSG4b7G/5tZayqbXt6Y4/zGbr+giQ0w3mrRHmvudetwRZv30NH +dW0WAYN6VD8+4TFYnC5uRl2nIE9fCGNviPBfqmOmpttVeSMbWcZHYramq6oeD96r +MzF/KqlLVYxgqJMmE/S6/od9TLYqY6JuRsF5J583R5+W3Yfokm5KQloyL5FwfA9G +vBnWd9n+NyZmapYM8iGKshfXMsbLsD5GvxMYdrEJnat/mtRjeLOmtgp17y0u7hjn +d0FqWTso1gU0TCkOQkniPZh0ZGlTNH1ZLqhVjUgrOGanjizagFC120cpfPGVvgtf +kyLaPq8mhqxiomfyfrXKAMNeUSEsT/laDuxVABEBAAG0LkRlbW8gU2lnbmluZyBT +ZXJ2aWNlIDxzaWduaW5nQHB1bHAtZGVtby5sb2NhbD6JAVIEEwEKADwWIQQIaVf3 +Lk52bZ8NoXxxdjKb8+VGewUCaMg+dAMbLwQFCwkIBwICIgIGFQoJCAsCBBYCAwEC +HgcCF4AACgkQcXYym/PlRnvN2wf/XIZmHjIRJIM0yfjCXmChRtEySFj6D3OrnNQB +AG3kTNRMIm4HiH/bY9Rc0hGkl7Q636Z0GAc79HX7UMb7kebTRByneyo5/S4cvel6 +S0wulEK5Tq45yDX4hczD+8wrO0u8Ma2CrFiK5LXn0up5A76SYzvaSCHgfjzC9adD +y5HLwtw1TiZMEvFIFZlPaG2ea8wXOsVyTALdIISIMi00ecfILHnR2in2KbmfiNqc +cdxaWCrow8E+MdeWW084CIGOtAzS687IWTDj8iAUMic3vSWBmS6/DAVSKbTyFDfe +OylY5s/Q34G2Fr3neqSB04TwvGIEykuBGy3Kr60aYK/aBrEGIQ== +=PwWq +-----END PGP PUBLIC KEY BLOCK----- diff --git a/demo/assets/keys/gpg/pubring.kbx b/demo/assets/keys/gpg/pubring.kbx new file mode 100644 index 0000000000000000000000000000000000000000..5b775ba0ea2bef70faef0bd4b93dc0873c1387ee GIT binary patch literal 843 zcmZQzU{GLWWMJ}kib!Jsf{YV(B_Ir9Ljbcj6C;=v$H2g}lYx;zh9filyPjWJ?tI>b zHHBqHvp+v|s|Ko2fT#zm;9+23tON4&z#@zcEKnIZ1+!r$BQFbx&B(zpNxJQ~^uOHH zHLG%~tCIUaOYZvDq+)WoaR*7Cx0A@OIYZ3(a0m>7k}~UIae4Na-aBI z@lncS@2J0>d?n_R;F9TEtnM!>kzlv}Qu;gK*zbwYF1yGquhz2st0%MfWtF#&-V~>u ze(NkxnEq%zbNKfeZENp6hHIPFM07pb`ux(XO6Gl&lC5ufC_bpOIrMe8^XZAF58W~K zQ}Yq}=pj1OKO=Wu_8D=jqeVVUcNJPXjC4&ZkDt(~ym(9Vo952>9k=Eb-ieCPdU5!m zo#FLq(f$@3jeT2qF1q1(^;V IjqJ1l0P0Xbk^lez literal 0 HcmV?d00001 diff --git a/demo/assets/keys/gpg/pubring.kbx~ b/demo/assets/keys/gpg/pubring.kbx~ new file mode 100644 index 0000000000000000000000000000000000000000..20bf8b33832543a3b69d53caa488904aed21be3a GIT binary patch literal 32 ecmZQzU{GLWWMJ}kib!Jsf{YV(B_Ir9g8%?fg#|PK literal 0 HcmV?d00001 diff --git a/demo/assets/keys/gpg/trustdb.gpg b/demo/assets/keys/gpg/trustdb.gpg new file mode 100644 index 0000000000000000000000000000000000000000..547769f8fa46b33c6acccebeb8751248e48b5172 GIT binary patch literal 1280 zcmZQfFGy!*W@Ke#VqnO)bz~_6cEHGmT^vQI9Y#v2V6SV*AKqv3)Y z9x&cW3l|;+j?D1ydVXcO^LZE66qXsy{`}Ofnhg>h@~}{0VBlr=rWMZkzftDa&%Bkq S+4^dMEz8%cK-D3XF#rJ5-WSUN literal 0 HcmV?d00001 diff --git a/assets/packages/hello_2.10-2_amd64.deb b/demo/assets/packages/hello_2.10-2_amd64.deb similarity index 100% rename from assets/packages/hello_2.10-2_amd64.deb rename to demo/assets/packages/hello_2.10-2_amd64.deb diff --git a/assets/scripts/deb_sign.sh b/demo/assets/scripts/deb_sign.sh similarity index 100% rename from assets/scripts/deb_sign.sh rename to demo/assets/scripts/deb_sign.sh diff --git a/assets/scripts/signing_service.py b/demo/assets/scripts/signing_service.py similarity index 100% rename from assets/scripts/signing_service.py rename to demo/assets/scripts/signing_service.py diff --git a/docker/simple/config.ini b/demo/config.ini similarity index 100% rename from docker/simple/config.ini rename to demo/config.ini diff --git a/docker/simple-cluster.yml b/demo/docker-compose.yml similarity index 66% rename from docker/simple-cluster.yml rename to demo/docker-compose.yml index cb484ea..e89858d 100644 --- a/docker/simple-cluster.yml +++ b/demo/docker-compose.yml @@ -7,13 +7,10 @@ services: - POSTGRES_PASSWORD=password - PULP_DEFAULT_ADMIN_PASSWORD=password volumes: - - "./simple/pulp-primary/storage:/var/lib/pulp" - - "./simple/pulp-primary/pgsql:/var/lib/pgsql" - - "./simple/pulp-primary/containers:/var/lib/containers" - - "./simple/settings/settings-primary.py:/etc/pulp/settings.py:z" - - "../assets/certs/database_fields.symmetric.key:/etc/pulp/database_fields.symmetric.key:z" - - "../assets/scripts:/opt/scripts:z" - - "../assets/keys/gpg:/opt/gpg:z" + - "./settings/settings-primary.py:/etc/pulp/settings.py:z" + - "./assets/certs/database_fields.symmetric.key:/etc/pulp/database_fields.symmetric.key:z" + - "./assets/scripts:/opt/scripts:z" + - "./assets/keys/gpg:/opt/gpg:z" devices: - "/dev/fuse" networks: @@ -27,13 +24,10 @@ services: - POSTGRES_PASSWORD=password - PULP_DEFAULT_ADMIN_PASSWORD=password volumes: - - "./simple/pulp-secondary/storage:/var/lib/pulp" - - "./simple/pulp-secondary/pgsql:/var/lib/pgsql" - - "./simple/pulp-secondary/containers:/var/lib/containers" - - "./simple/settings/settings-secondary.py:/etc/pulp/settings.py:z" - - "../assets/certs/database_fields.symmetric.key:/etc/pulp/database_fields.symmetric.key:z" - - "../assets/scripts:/opt/scripts:z" - - "../assets/keys/gpg:/opt/gpg:z" + - "./settings/settings-secondary.py:/etc/pulp/settings.py:z" + - "./assets/certs/database_fields.symmetric.key:/etc/pulp/database_fields.symmetric.key:z" + - "./assets/scripts:/opt/scripts:z" + - "./assets/keys/gpg:/opt/gpg:z" devices: - "/dev/fuse" networks: @@ -71,8 +65,8 @@ services: DB_USER: pulp-manager DB_PASSWORD: pulp-manager JWT_SECRET: test_secret - PULP_MANAGER_CONFIG_PATH: /pulp_manager/docker/simple/config.ini - PULP_SYNC_CONFIG_PATH: /pulp_manager/docker/simple/pulp-config.yml + PULP_MANAGER_CONFIG_PATH: /pulp_manager/demo/config.ini + PULP_SYNC_CONFIG_PATH: /pulp_manager/demo/pulp-config.yml Is_local: true depends_on: - mariadb @@ -90,8 +84,8 @@ services: DB_USER: pulp-manager DB_PASSWORD: pulp-manager JWT_SECRET: test_secret - PULP_MANAGER_CONFIG_PATH: /pulp_manager/docker/simple/config.ini - PULP_SYNC_CONFIG_PATH: /pulp_manager/docker/simple/pulp-config.yml + PULP_MANAGER_CONFIG_PATH: /pulp_manager/demo/config.ini + PULP_SYNC_CONFIG_PATH: /pulp_manager/demo/pulp-config.yml Is_local: true depends_on: - mariadb @@ -112,8 +106,8 @@ services: DB_USER: pulp-manager DB_PASSWORD: pulp-manager JWT_SECRET: test_secret - PULP_MANAGER_CONFIG_PATH: /pulp_manager/docker/simple/config.ini - PULP_SYNC_CONFIG_PATH: /pulp_manager/docker/simple/pulp-config.yml + PULP_MANAGER_CONFIG_PATH: /pulp_manager/demo/config.ini + PULP_SYNC_CONFIG_PATH: /pulp_manager/demo/pulp-config.yml Is_local: true depends_on: - redis-manager @@ -130,8 +124,8 @@ services: DB_USER: pulp-manager DB_PASSWORD: pulp-manager JWT_SECRET: test_secret - PULP_MANAGER_CONFIG_PATH: /pulp_manager/docker/simple/config.ini - PULP_SYNC_CONFIG_PATH: /pulp_manager/docker/simple/pulp-config.yml + PULP_MANAGER_CONFIG_PATH: /pulp_manager/demo/config.ini + PULP_SYNC_CONFIG_PATH: /pulp_manager/demo/pulp-config.yml Is_local: true depends_on: - mariadb @@ -147,8 +141,8 @@ services: pip install flask && python /app/signing_service.py" volumes: - - "../assets/scripts/signing_service.py:/app/signing_service.py:z" - - "../assets/keys/gpg:/app/gpg:z" + - "./assets/scripts/signing_service.py:/app/signing_service.py:z" + - "./assets/keys/gpg:/app/gpg:z" environment: - GNUPGHOME=/app/gpg networks: diff --git a/docker/simple/pulp-config.yml b/demo/pulp-config.yml similarity index 100% rename from docker/simple/pulp-config.yml rename to demo/pulp-config.yml diff --git a/demo/settings/certs/database_fields.symmetric.key b/demo/settings/certs/database_fields.symmetric.key new file mode 100644 index 0000000..f4f65d9 --- /dev/null +++ b/demo/settings/certs/database_fields.symmetric.key @@ -0,0 +1 @@ +VT4Tw4ZtqN7ur+IT7gMFw0BCXRIypT6PvDZUsI5BdOk= diff --git a/demo/settings/certs/pulp_webserver.crt b/demo/settings/certs/pulp_webserver.crt new file mode 100644 index 0000000..65a89ed --- /dev/null +++ b/demo/settings/certs/pulp_webserver.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDzDCCArSgAwIBAgIUdlvuFhAgdG7q5+57TP2CpSkNYgMwDQYJKoZIhvcNAQEL +BQAwYTENMAsGA1UEAwwEcHVscDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5DMRAw +DgYDVQQHDAdSYWxlaWdoMRYwFAYDVQQKDA1SZWQgSGF0LCBJbmMuMQwwCgYDVQQL +DANwbnQwHhcNMjUwOTE1MTQ0NzU4WhcNMjYwOTE1MTQ0NzU4WjBhMQ0wCwYDVQQD +DARwdWxwMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxEDAOBgNVBAcMB1JhbGVp +Z2gxFjAUBgNVBAoMDVJlZCBIYXQsIEluYy4xDDAKBgNVBAsMA3BudDCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBANulUPnGL/p0ga2F9gKngcU+AUQEEm1F +0aiiKrY93hv26ZSPBThYUmheTReBA7ha1kT9gnmIe4Z7m/wAfqp+ML1+zxz7O8lW +OpdGn5LbKcO8I+k8ngIgAFTXLa1i5XL5/MveZ+WMjSWmHn/BSVbJWx8nBELDeYoi +VOgitNXIepGRrWgs/6VM2+L+nqsUPWn1reJ1I2SzniQE5WdKmEMJ/3qd2jNn0OoW +KITg30Gp5+WeOUva6lCEuitTYK8QIGuqacYAgxC+4JuNRnl8AVy48/OKXslwsHQt +1reMmxRktfYhvjawfNa9zN9z5v/RfOFdWEm8UU+w/3tStjgmpEag508CAwEAAaN8 +MHowWQYDVR0RBFIwUIIEcHVscIIQcHVscC5leGFtcGxlLmNvbYILY2kucHVscC5j +b22CBmdhbGF4eYISZ2FsYXh5LmV4YW1wbGUuY29tgg1jaS5nYWxheHkuY29tMB0G +A1UdDgQWBBQc0Va5pAmSTfcc4GplB5kartoL6TANBgkqhkiG9w0BAQsFAAOCAQEA +LWKXqUKbBddUSN4JDkUosAYkoNGo2yYMEGExEQ6VSimnyUSFypWfpR/XdIGqO9IL +Rbfe+5N7gkR+bfaTvMyvjpL9rOVwIBQBwV9t7T30xpsxmLRpDegWTn94sXUcmdwr +w0k4D3HgwznlFgew0nLlrfXWoKE44WxTKp/ClXoFp7k1FtxA7MrCHcgSWgGO1kni +G2+bf3UlzkLjLkUZ3HrqD3KQoXgHKTk7LHBWN7pNU6BbLBz+LTeTW7vqecxjGuvx +qWOP4MK0SNY/gQeOQd2DBCWFJFOVaKhgrNOrNMHHltWsgl6CGxG0SYNDNKCCmM9j +x+NGA4DfCOBb1yWmsYHQ/A== +-----END CERTIFICATE----- diff --git a/demo/settings/certs/pulp_webserver.csr b/demo/settings/certs/pulp_webserver.csr new file mode 100644 index 0000000..451d078 --- /dev/null +++ b/demo/settings/certs/pulp_webserver.csr @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIDPjCCAiYCAQAwYTENMAsGA1UEAwwEcHVscDELMAkGA1UEBhMCVVMxCzAJBgNV +BAgMAk5DMRAwDgYDVQQHDAdSYWxlaWdoMRYwFAYDVQQKDA1SZWQgSGF0LCBJbmMu +MQwwCgYDVQQLDANwbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDb +pVD5xi/6dIGthfYCp4HFPgFEBBJtRdGooiq2Pd4b9umUjwU4WFJoXk0XgQO4WtZE +/YJ5iHuGe5v8AH6qfjC9fs8c+zvJVjqXRp+S2ynDvCPpPJ4CIABU1y2tYuVy+fzL +3mfljI0lph5/wUlWyVsfJwRCw3mKIlToIrTVyHqRka1oLP+lTNvi/p6rFD1p9a3i +dSNks54kBOVnSphDCf96ndozZ9DqFiiE4N9BqeflnjlL2upQhLorU2CvECBrqmnG +AIMQvuCbjUZ5fAFcuPPzil7JcLB0Lda3jJsUZLX2Ib42sHzWvczfc+b/0XzhXVhJ +vFFPsP97UrY4JqRGoOdPAgMBAAGggZcwgZQGCSqGSIb3DQEJDjGBhjCBgzAJBgNV +HRMEAjAAMAsGA1UdDwQEAwIF4DBpBgNVHREEYjBgggRwdWxwghBwdWxwLmV4YW1w +bGUuY29tggtjaS5wdWxwLmNvbYIGZ2FsYXh5ghJnYWxheHkuZXhhbXBsZS5jb22C +DWNpLmdhbGF4eS5jb22CDnB1bHAtZXBoZW1lcmFsMA0GCSqGSIb3DQEBCwUAA4IB +AQAIwgjet3bR+VCAitth0D37NuVFNepLnBukaS4eEpODbbocMrBfSSTWgoWyZcx3 +/hQBjcLFhubrxrPIDNRp0XOubJP25AnYOBurgpyF8aBeQch3itIaPrXCud9jxcI5 +vzcXcqe+X+hL106VSIM50X/0ljr8QBF0QPvHGO2LfUbDSkxvuO+8+k6MKdY5DWoY +/87EFtAsYaVY/Ln/gbYUxYnG1gQT0ar3mH6IC5hefOfmgNAx5AkXnfATR/TMpqVd +K1hTOEs9PcSssHq1TlmopuDYwjldlmEnVjRpydzIdBeez+bq2GQU9qgO5I6NdRul +xdtKfUMldG7oqw4K6k9bWE35 +-----END CERTIFICATE REQUEST----- diff --git a/demo/settings/certs/pulp_webserver.key b/demo/settings/certs/pulp_webserver.key new file mode 100644 index 0000000..52e7c84 --- /dev/null +++ b/demo/settings/certs/pulp_webserver.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDbpVD5xi/6dIGt +hfYCp4HFPgFEBBJtRdGooiq2Pd4b9umUjwU4WFJoXk0XgQO4WtZE/YJ5iHuGe5v8 +AH6qfjC9fs8c+zvJVjqXRp+S2ynDvCPpPJ4CIABU1y2tYuVy+fzL3mfljI0lph5/ +wUlWyVsfJwRCw3mKIlToIrTVyHqRka1oLP+lTNvi/p6rFD1p9a3idSNks54kBOVn +SphDCf96ndozZ9DqFiiE4N9BqeflnjlL2upQhLorU2CvECBrqmnGAIMQvuCbjUZ5 +fAFcuPPzil7JcLB0Lda3jJsUZLX2Ib42sHzWvczfc+b/0XzhXVhJvFFPsP97UrY4 +JqRGoOdPAgMBAAECggEAXDzYvANzy4AYoweiD2xLjUqkGm2BVX0f9eqf+CcT+nde +kOHcpdRFSwnq4udtagJPf+pb2/CqOwf2scV9a9iRXEp/7GI+saax9SwVwASkTcvY +4s3vZuIMcZRpXKouw/FiLTTpOZckAP75SNohE758MyFd4zGWfSE8ub01Zp+JhU45 +2yOMVs0nJWDFNx5EeknCu32muWtAsuHhn8nFEVR854jIpiXXMIA4x4mGqtiJ7dsw +4FPGiMtbr2LGdlEt5fJ41FUzsQL/I4AB3yzbzBN/WD9kl6DxazHN9cEiemwG6yLq +1+BE0+RyWZkzw0KCwB8Z3msEinvfHmgvf+AYEJoGYQKBgQD7lJ0mMmN7glxVecdH +obfeXJIHRS930vXzqtpi6ta5XdOefCPRf3FHy/kOQmx3XAjPz5zqjtxhFwpfTPOo +v9F65mDi/lcG+PwPI/avvb5QHaDZGvqSOT3iRp4ZtiOr4En8iX/A5lzyS724dy6T +sdNfFI1DhcIyK22VNWNG4g3i4QKBgQDfgRaWIg0N2MgaAWfdXUlzoMQNwXv9qpsy +cU76ndetO3JvtFJaGMnzx4B8OLDV3PWZmUuFQWoYgiY6MXh+NZNzWhKHyZptaIzq +TdN2stvkwC5R5xheiExoqYOEsDseyiaZVPFmm3RvwSaKK8O0UBxUECZQr/2pI64/ +xEXM1NxALwKBgQC2DovSknBPdrze1iuR+MOYVKEQRc7eBXobBS7Yml0c83sqm2dI +OdN+Q2tOGF9p2N4/lFxXmV59nDDTWlqRaY5sQ78lRS0xTIkLY9kmwEayr14w/kcJ +/gZ8cuICdT5HKR4hdFdQ/uOQK6N1lDnOg8cUbUj07hzkNW0tpt7a8sddwQKBgQDW +83p6d1Mgrw17XUC797ztlH8ZT6zkwJC0CZ9Qjj9f3p1nawAMoGyRpJwgXBFODUyz +JWgpR0k3Ouxy6SMeFn6x4L2TlEON15A2wxSNwuSScnXZ1Sxtth3uRqEzGp4xNW+7 +5aKo8Pchh5x+JCr6nlUwT7vZu/h0E9nAgA41Ob59hwKBgQDYj1z2MusWB6/c8O1T +CQ3JYMzXqdYbHSozxXlBRA0N0oUuWzIMQXHfH+QQFxEjaQRdTaBLpgp6J4OXM1ia +pGdPGFnxHw63bkCjGDJ0UomoD8CHkdecL50ZLwqKtD8tGM+kscE1RO7txtObxC/P +/n2x41QA87SWlsbkCk9iVGrrVQ== +-----END PRIVATE KEY----- diff --git a/demo/settings/certs/token_private_key.pem b/demo/settings/certs/token_private_key.pem new file mode 100644 index 0000000..c9bf840 --- /dev/null +++ b/demo/settings/certs/token_private_key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEINgU0fyK12OLF9IUfASKtEzE+xUYYfx2TZZF8c9n+I6voAoGCCqGSM49 +AwEHoUQDQgAERfGstBI/1n9aquPqaoI1jJ8JJrVZ6dSTNl7sRa3tKt1KBEfA1AIt +8qA5gZKo1sWQy2Rgr3Kzgrr2sCtpPlR3Qw== +-----END EC PRIVATE KEY----- diff --git a/demo/settings/certs/token_public_key.pem b/demo/settings/certs/token_public_key.pem new file mode 100644 index 0000000..e89ea22 --- /dev/null +++ b/demo/settings/certs/token_public_key.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERfGstBI/1n9aquPqaoI1jJ8JJrVZ +6dSTNl7sRa3tKt1KBEfA1AIt8qA5gZKo1sWQy2Rgr3Kzgrr2sCtpPlR3Qw== +-----END PUBLIC KEY----- diff --git a/demo/settings/database_fields.symmetric.key b/demo/settings/database_fields.symmetric.key new file mode 100644 index 0000000..f73cd1f --- /dev/null +++ b/demo/settings/database_fields.symmetric.key @@ -0,0 +1 @@ +XlN6mYFXLtvJ30fQXvR96UxTNf+sNHbDmAERi7CWRf0= diff --git a/docker/simple/settings/settings-primary.py b/demo/settings/settings-primary.py similarity index 100% rename from docker/simple/settings/settings-primary.py rename to demo/settings/settings-primary.py diff --git a/docker/simple/settings/settings-secondary.py b/demo/settings/settings-secondary.py similarity index 100% rename from docker/simple/settings/settings-secondary.py rename to demo/settings/settings-secondary.py diff --git a/docker/simple/settings/settings.py b/demo/settings/settings.py similarity index 100% rename from docker/simple/settings/settings.py rename to demo/settings/settings.py diff --git a/pytest.ini b/pytest.ini index d3ff18a..daa5d7c 100644 --- a/pytest.ini +++ b/pytest.ini @@ -7,4 +7,4 @@ env = JWT_SECRET=test_secret PULP_MANAGER_SKIP_PARSER_CONFIG=1 PULP_MANAGER_CONFIG_PATH=./.devcontainer/test_config.ini - PULP_SYNC_CONFIG_PATH=./docker/simple/pulp-config.yml + PULP_SYNC_CONFIG_PATH=./demo/pulp-config.yml diff --git a/setup-demo.sh b/setup-demo.sh index ea4ad30..0dc2e84 100755 --- a/setup-demo.sh +++ b/setup-demo.sh @@ -14,9 +14,9 @@ echo "=================================" check_server_running() { local server="$1" local server_name="$2" - + echo "Checking if $server_name is running..." - + if ! curl -s --connect-timeout 5 "$server/pulp/api/v3/status/" > /dev/null 2>&1; then echo "ERROR: $server_name at $server is not accessible!" echo "" @@ -27,32 +27,11 @@ check_server_running() { echo "" return 1 fi - + echo "$server_name is running and accessible" return 0 } -# Function to check if a resource exists by name -check_exists() { - local url="$1" - local name="$2" - local response=$(curl -s -u $PULP_USER:$PULP_PASS "$url?name=$name" 2>/dev/null) - - # Check if curl failed or returned empty response - if [ -z "$response" ] || ! echo "$response" | jq . > /dev/null 2>&1; then - echo "Warning: Could not connect to server or invalid JSON response" - return 1 - fi - - local count=$(echo "$response" | jq -r '.count // 0' 2>/dev/null) - # Ensure count is a valid number - if [[ "$count" =~ ^[0-9]+$ ]] && [ "$count" -gt 0 ]; then - return 0 - else - return 1 - fi -} - # Function to wait for task completion wait_for_task() { local task_href="$1" @@ -85,119 +64,6 @@ wait_for_task() { done } -# Function to setup repository and remote on a server -setup_repo_and_remote() { - local server="$1" - local repo_name="$2" - local repo_desc="$3" - local remote_name="$4" - local remote_url="$5" - local distributions="$6" - local components="$7" - local architectures="$8" - - echo " Setting up $repo_name on $(basename $server)..." - - # Create repository with proper description format for Pulp Manager - # The base_url in description should be just the repo name (used for base_path) - # Not the full remote URL - local base_url="$repo_name" - local full_description="base_url: $base_url" - if [ -n "$repo_desc" ]; then - full_description="$full_description\ndescription: $repo_desc" - fi - - if check_exists "$server/pulp/api/v3/repositories/deb/apt/" "$repo_name"; then - echo " Repository '$repo_name' already exists" - REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$server/pulp/api/v3/repositories/deb/apt/?name=$repo_name") - REPO_HREF=$(echo "$REPO_RESPONSE" | jq -r '.results[0].pulp_href') - - # Update the description to have proper format - echo " Updating repository description..." - curl -s -u $PULP_USER:$PULP_PASS -X PATCH "$server$REPO_HREF" \ - -H "Content-Type: application/json" \ - -d "{\"description\": \"$full_description\"}" > /dev/null - else - echo " Creating repository '$repo_name'..." - REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$server/pulp/api/v3/repositories/deb/apt/" \ - -H "Content-Type: application/json" \ - -d "{\"name\": \"$repo_name\", \"description\": \"$full_description\"}") - REPO_HREF=$(echo "$REPO_RESPONSE" | jq -r '.pulp_href') - echo " Repository created: $REPO_HREF" - fi - - # Only create remotes and associate them for secondary server (when remote_url is provided) - if [ -n "$remote_url" ]; then - # Create remote - if check_exists "$server/pulp/api/v3/remotes/deb/apt/" "$remote_name"; then - echo " Remote '$remote_name' already exists" - REMOTE_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$server/pulp/api/v3/remotes/deb/apt/?name=$remote_name") - REMOTE_HREF=$(echo "$REMOTE_RESPONSE" | jq -r '.results[0].pulp_href') - else - echo " Creating remote '$remote_name'..." - remote_data="{\"name\": \"$remote_name\", \"url\": \"$remote_url\", \"distributions\": \"$distributions\"" - if [ -n "$components" ]; then - remote_data="$remote_data, \"components\": \"$components\"" - fi - if [ -n "$architectures" ]; then - remote_data="$remote_data, \"architectures\": \"$architectures\"" - fi - remote_data="$remote_data}" - - REMOTE_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$server/pulp/api/v3/remotes/deb/apt/" \ - -H "Content-Type: application/json" \ - -d "$remote_data") - REMOTE_HREF=$(echo "$REMOTE_RESPONSE" | jq -r '.pulp_href') - echo " Remote created: $REMOTE_HREF" - fi - - # Associate remote with repository - echo " Associating remote with repository..." - REPO_UPDATE_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X PATCH "$server$REPO_HREF" \ - -H "Content-Type: application/json" \ - -d "{\"remote\": \"$REMOTE_HREF\"}") - - if echo "$REPO_UPDATE_RESPONSE" | jq -e '.task' > /dev/null; then - TASK_HREF=$(echo "$REPO_UPDATE_RESPONSE" | jq -r '.task') - wait_for_task "$TASK_HREF" "$server" - fi - fi - - echo "REPO_HREF_${repo_name//-/_}=$REPO_HREF" - - # Export variables for later use - export "REPO_HREF_${repo_name//-/_}"="$REPO_HREF" - if [ -n "$remote_url" ]; then - export "REMOTE_HREF_${repo_name//-/_}"="$REMOTE_HREF" - fi -} - -# Function to update pulp-manager database with remote associations -update_pulp_manager_db() { - local server_id="$1" - local repo_name="$2" - local remote_href="$3" - - echo " Updating pulp-manager database for $repo_name on server $server_id..." - - # Wait for pulp-manager to discover the repository first - sleep 5 - - # Get the pulp-manager repo ID by querying repos for the server - local pm_repo_response=$(curl -s "http://localhost:8080/v1/pulp_servers/$server_id/repos") - local pm_repo_id=$(echo "$pm_repo_response" | jq -r ".items[] | select(.name == \"$repo_name\") | .id") - - if [ -n "$pm_repo_id" ] && [ "$pm_repo_id" != "null" ]; then - # Update the database directly - docker exec docker-mariadb-1 mariadb -u pulp-manager -ppulp-manager pulp_manager \ - -e "UPDATE pulp_server_repos SET remote_href = '$remote_href' WHERE id = $pm_repo_id;" - echo " Updated pulp-manager database: repo ID $pm_repo_id -> $remote_href" - else - echo " Could not find repo $repo_name in pulp-manager database for server $server_id" - echo " Available repos: $(echo "$pm_repo_response" | jq -r '.items[].name' | tr '\n' ' ')" - fi -} - echo "" echo "Checking server connectivity..." check_server_running "$PULP_PRIMARY" "Pulp Primary" @@ -224,20 +90,20 @@ assign_signing_service_to_repo() { register_signing_service() { local container_name="$1" local server_name="$2" - + echo " Checking if signing service exists on $server_name..." - + # Check if signing service already exists in the database existing=$(docker exec $container_name bash -c "pulpcore-manager shell -c \"from pulpcore.app.models import SigningService; print(SigningService.objects.filter(name='deb_signing_service').exists())\"" 2>/dev/null) - + if [ "$existing" = "True" ]; then echo " Signing service 'deb_signing_service' already exists on $server_name" else echo " Creating signing service 'deb_signing_service' on $server_name..." - + # Get the GPG key ID from the mounted keyring key_id=$(docker exec $container_name bash -c "GNUPGHOME=/opt/gpg gpg --list-secret-keys --with-colons 2>/dev/null | grep '^sec:' | cut -d: -f5 | head -1") - + if [ -n "$key_id" ]; then # Add the signing service using the management command docker exec $container_name bash -c "pulpcore-manager add-signing-service \ @@ -260,36 +126,33 @@ register_signing_service "docker-pulp-primary-1" "Pulp Primary" register_signing_service "docker-pulp-secondary-1" "Pulp Secondary" echo "" -echo "Step 1: Setting up Primary Server Repositories" -echo "==============================================" - -# Setup external repo on primary with upstream remote -setup_repo_and_remote "$PULP_PRIMARY" "ext-small-repo" "External small Debian testing repository" \ - "debian-testing-remote" "http://deb.debian.org/debian/" "testing" "main" "amd64" -EXT_REPO_HREF=$REPO_HREF - -# Setup internal repo on primary (no remote needed) -setup_repo_and_remote "$PULP_PRIMARY" "int-demo-packages" "Internal demo repository" "" "" "" "" "" -INT_REPO_HREF=$REPO_HREF -# Assign signing service to internal repo on primary -assign_signing_service_to_repo "$PULP_PRIMARY" "$INT_REPO_HREF" "int-demo-packages" - -echo "" -echo "Step 2: Setting up Secondary Server Repositories" -echo "===============================================" +echo "Step 1: Getting Repository References" +echo "=====================================" + +# Get repository hrefs (assumes repos already exist in Pulp) +echo " Getting int-demo-packages repository..." +REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/?name=int-demo-packages") +INT_REPO_HREF=$(echo "$REPO_RESPONSE" | jq -r '.results[0].pulp_href') + +if [ -z "$INT_REPO_HREF" ] || [ "$INT_REPO_HREF" = "null" ]; then + echo " ERROR: Repository 'int-demo-packages' not found on primary server" + echo " Please create repositories manually or via Pulp Manager before running this script" + exit 1 +fi +echo " Found: $INT_REPO_HREF" -# Setup repos on secondary that sync from primary -setup_repo_and_remote "$PULP_SECONDARY" "int-demo-packages" "Internal demo packages synced from primary" \ - "int-demo-remote" "http://docker-pulp-primary-1/pulp/content/int-demo-packages/" "stable" "" "" -SECONDARY_INT_REPO_HREF=$REPO_HREF -# Assign signing service to internal repo on secondary -assign_signing_service_to_repo "$PULP_SECONDARY" "$SECONDARY_INT_REPO_HREF" "int-demo-packages" +echo " Getting ext-small-repo repository..." +REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/?name=ext-small-repo") +EXT_REPO_HREF=$(echo "$REPO_RESPONSE" | jq -r '.results[0].pulp_href') -setup_repo_and_remote "$PULP_SECONDARY" "ext-small-repo" "External small repo synced from primary" \ - "ext-small-remote" "http://docker-pulp-primary-1/pulp/content/ext-small-repo/" "testing" "main" "amd64" +if [ -z "$EXT_REPO_HREF" ] || [ "$EXT_REPO_HREF" = "null" ]; then + echo " ERROR: Repository 'ext-small-repo' not found on primary server" + exit 1 +fi +echo " Found: $EXT_REPO_HREF" echo "" -echo "Step 3: Uploading Demo Package to Internal Repository" +echo "Step 2: Uploading Demo Package to Internal Repository" echo "=====================================================" # Check if package already exists in repository @@ -308,7 +171,7 @@ else echo " Uploading demo package..." CONTENT_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/content/deb/packages/" \ -H "Content-Type: multipart/form-data" \ - -F "file=@assets/packages/hello_2.10-2_amd64.deb") + -F "file=@demo/assets/packages/hello_2.10-2_amd64.deb") UPLOAD_TASK_HREF=$(echo "$CONTENT_RESPONSE" | jq -r '.task') wait_for_task "$UPLOAD_TASK_HREF" "$PULP_PRIMARY" @@ -329,98 +192,52 @@ else fi echo "" -echo "Step 4: Creating Publications" -echo "=============================" - -# Create publication for external repository -echo " Creating publication for ext-small-repo..." -EXT_PUB_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/publications/deb/apt/" \ - -H "Content-Type: application/json" \ - -d "{\"repository\": \"$EXT_REPO_HREF\"}") - -if echo "$EXT_PUB_RESPONSE" | jq -e '.task' > /dev/null; then - EXT_PUB_TASK=$(echo "$EXT_PUB_RESPONSE" | jq -r '.task') - wait_for_task "$EXT_PUB_TASK" "$PULP_PRIMARY" - - # Get publication href - PUB_TASK_RESULT=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY$EXT_PUB_TASK") - EXT_PUB_HREF=$(echo "$PUB_TASK_RESULT" | jq -r '.created_resources[0]') - echo " External publication created: $EXT_PUB_HREF" -else - EXT_PUB_HREF=$(echo "$EXT_PUB_RESPONSE" | jq -r '.pulp_href') - echo " External publication exists: $EXT_PUB_HREF" -fi +echo "Step 3: Creating Publications and Distributions" +echo "===============================================" # Create publication for internal repository -echo " Creating publication for int-demo-packages..." +echo " Creating publication for int-demo-packages..." INT_PUB_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/publications/deb/apt/" \ -H "Content-Type: application/json" \ - -d "{\"repository\": \"$INT_REPO_HREF\"}") + -d "{\"repository\": \"$INT_REPO_HREF\", \"simple\": true}") if echo "$INT_PUB_RESPONSE" | jq -e '.task' > /dev/null; then INT_PUB_TASK=$(echo "$INT_PUB_RESPONSE" | jq -r '.task') wait_for_task "$INT_PUB_TASK" "$PULP_PRIMARY" - + # Get publication href PUB_TASK_RESULT=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY$INT_PUB_TASK") INT_PUB_HREF=$(echo "$PUB_TASK_RESULT" | jq -r '.created_resources[0]') - echo " Internal publication created: $INT_PUB_HREF" -else - INT_PUB_HREF=$(echo "$INT_PUB_RESPONSE" | jq -r '.pulp_href') - echo " Internal publication exists: $INT_PUB_HREF" + echo " Publication created: $INT_PUB_HREF" fi -echo "" -echo "Step 5: Creating Distributions" -echo "==============================" +# Create or update distribution for internal repository +DIST_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/?name=int-demo-packages") +DIST_EXISTS=$(echo "$DIST_RESPONSE" | jq -r '.count') -# Create distribution for external repository -if check_exists "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/" "ext-small-repo"; then - echo " Distribution 'ext-small-repo' already exists" -else - echo " Creating distribution for ext-small-repo..." - EXT_DIST_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/" \ +if [ "$DIST_EXISTS" -gt 0 ]; then + echo " Updating distribution for int-demo-packages..." + DIST_HREF=$(echo "$DIST_RESPONSE" | jq -r '.results[0].pulp_href') + DIST_UPDATE=$(curl -s -u $PULP_USER:$PULP_PASS -X PATCH "$PULP_PRIMARY$DIST_HREF" \ -H "Content-Type: application/json" \ - -d "{ - \"name\": \"ext-small-repo\", - \"base_path\": \"ext-small-repo\", - \"publication\": \"$EXT_PUB_HREF\" - }") - - if echo "$EXT_DIST_RESPONSE" | jq -e '.task' > /dev/null; then - EXT_DIST_TASK=$(echo "$EXT_DIST_RESPONSE" | jq -r '.task') - wait_for_task "$EXT_DIST_TASK" "$PULP_PRIMARY" - fi - echo " External distribution created" -fi + -d "{\"publication\": \"$INT_PUB_HREF\"}") -# Create distribution for internal repository -if check_exists "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/" "int-demo-packages"; then - echo " Distribution 'int-demo-packages' already exists" + if echo "$DIST_UPDATE" | jq -e '.task' > /dev/null; then + DIST_TASK=$(echo "$DIST_UPDATE" | jq -r '.task') + wait_for_task "$DIST_TASK" "$PULP_PRIMARY" + fi else - echo " Creating distribution for int-demo-packages..." - INT_DIST_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/" \ + echo " Creating distribution for int-demo-packages..." + DIST_CREATE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/" \ -H "Content-Type: application/json" \ - -d "{ - \"name\": \"int-demo-packages\", - \"base_path\": \"int-demo-packages\", - \"publication\": \"$INT_PUB_HREF\" - }") - - if echo "$INT_DIST_RESPONSE" | jq -e '.task' > /dev/null; then - INT_DIST_TASK=$(echo "$INT_DIST_RESPONSE" | jq -r '.task') - wait_for_task "$INT_DIST_TASK" "$PULP_PRIMARY" + -d "{\"name\": \"int-demo-packages\", \"base_path\": \"int-demo-packages\", \"publication\": \"$INT_PUB_HREF\"}") + + if echo "$DIST_CREATE" | jq -e '.task' > /dev/null; then + DIST_TASK=$(echo "$DIST_CREATE" | jq -r '.task') + wait_for_task "$DIST_TASK" "$PULP_PRIMARY" fi - echo " Internal distribution created" fi - -echo "" -echo "Step 6: Updating Pulp Manager Database" -echo "======================================" - -# Update pulp-manager database with remote associations for secondary server (ID=2) -update_pulp_manager_db "2" "int-demo-packages" "$REMOTE_HREF_int_demo_packages" -update_pulp_manager_db "2" "ext-small-repo" "$REMOTE_HREF_ext_small_repo" +echo " Distribution ready for int-demo-packages" echo "" echo " Demo Setup Complete!" From 977eb4d7e43ff1f3590c729a40fe54f42d2a4ca8 Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Fri, 3 Oct 2025 10:40:13 -0400 Subject: [PATCH 17/24] removed dupe test key material and updated gitignore/dockerignore Signed-off-by: Geoff Wilson --- .dockerignore | 10 +++---- .gitignore | 24 ++++------------ .../certs/database_fields.symmetric.key | 1 - demo/settings/certs/pulp_webserver.crt | 23 --------------- demo/settings/certs/pulp_webserver.csr | 20 ------------- demo/settings/certs/pulp_webserver.key | 28 ------------------- demo/settings/certs/token_private_key.pem | 5 ---- demo/settings/certs/token_public_key.pem | 4 --- demo/settings/database_fields.symmetric.key | 1 - 9 files changed, 9 insertions(+), 107 deletions(-) delete mode 100644 demo/settings/certs/database_fields.symmetric.key delete mode 100644 demo/settings/certs/pulp_webserver.crt delete mode 100644 demo/settings/certs/pulp_webserver.csr delete mode 100644 demo/settings/certs/pulp_webserver.key delete mode 100644 demo/settings/certs/token_private_key.pem delete mode 100644 demo/settings/certs/token_public_key.pem delete mode 100644 demo/settings/database_fields.symmetric.key diff --git a/.dockerignore b/.dockerignore index d7b52f5..6111977 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,8 +1,6 @@ -assets/keys/gpg/ -assets/certs/ +demo/assets/keys/gpg/ +demo/assets/certs/ .claude/ .coverage -docker/simple/pulp-primary/storage/ -docker/simple/pulp-primary/pgsql/ -docker/simple/pulp-secondary/storage/ -docker/simple/pulp-secondary/pgsql/ \ No newline at end of file +venv/ +__pycache__/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index e7bd70f..900e0fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,12 @@ __pycache__ venv -# Runtime data in docker/simple directories -docker/simple/pulp-primary/storage/* -docker/simple/pulp-primary/pgsql/* -docker/simple/pulp-primary/containers/* -docker/simple/pulp-secondary/storage/* -docker/simple/pulp-secondary/pgsql/* -docker/simple/pulp-secondary/containers/* -docker/simple/settings/certs/* - -# But keep the directories themselves -!docker/simple/pulp-primary/storage/.gitkeep -!docker/simple/pulp-primary/pgsql/.gitkeep -!docker/simple/pulp-primary/containers/.gitkeep -!docker/simple/pulp-secondary/storage/.gitkeep -!docker/simple/pulp-secondary/pgsql/.gitkeep -!docker/simple/pulp-secondary/containers/.gitkeep -!docker/simple/settings/certs/.gitkeep +# Demo environment runtime files +demo/assets/certs/* +!demo/assets/certs/.gitkeep +demo/assets/keys/gpg/* +!demo/assets/keys/.gitkeep # Other runtime files -assets/certs/ -assets/keys/ .coverage .claude/ diff --git a/demo/settings/certs/database_fields.symmetric.key b/demo/settings/certs/database_fields.symmetric.key deleted file mode 100644 index f4f65d9..0000000 --- a/demo/settings/certs/database_fields.symmetric.key +++ /dev/null @@ -1 +0,0 @@ -VT4Tw4ZtqN7ur+IT7gMFw0BCXRIypT6PvDZUsI5BdOk= diff --git a/demo/settings/certs/pulp_webserver.crt b/demo/settings/certs/pulp_webserver.crt deleted file mode 100644 index 65a89ed..0000000 --- a/demo/settings/certs/pulp_webserver.crt +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDzDCCArSgAwIBAgIUdlvuFhAgdG7q5+57TP2CpSkNYgMwDQYJKoZIhvcNAQEL -BQAwYTENMAsGA1UEAwwEcHVscDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5DMRAw -DgYDVQQHDAdSYWxlaWdoMRYwFAYDVQQKDA1SZWQgSGF0LCBJbmMuMQwwCgYDVQQL -DANwbnQwHhcNMjUwOTE1MTQ0NzU4WhcNMjYwOTE1MTQ0NzU4WjBhMQ0wCwYDVQQD -DARwdWxwMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxEDAOBgNVBAcMB1JhbGVp -Z2gxFjAUBgNVBAoMDVJlZCBIYXQsIEluYy4xDDAKBgNVBAsMA3BudDCCASIwDQYJ -KoZIhvcNAQEBBQADggEPADCCAQoCggEBANulUPnGL/p0ga2F9gKngcU+AUQEEm1F -0aiiKrY93hv26ZSPBThYUmheTReBA7ha1kT9gnmIe4Z7m/wAfqp+ML1+zxz7O8lW -OpdGn5LbKcO8I+k8ngIgAFTXLa1i5XL5/MveZ+WMjSWmHn/BSVbJWx8nBELDeYoi -VOgitNXIepGRrWgs/6VM2+L+nqsUPWn1reJ1I2SzniQE5WdKmEMJ/3qd2jNn0OoW -KITg30Gp5+WeOUva6lCEuitTYK8QIGuqacYAgxC+4JuNRnl8AVy48/OKXslwsHQt -1reMmxRktfYhvjawfNa9zN9z5v/RfOFdWEm8UU+w/3tStjgmpEag508CAwEAAaN8 -MHowWQYDVR0RBFIwUIIEcHVscIIQcHVscC5leGFtcGxlLmNvbYILY2kucHVscC5j -b22CBmdhbGF4eYISZ2FsYXh5LmV4YW1wbGUuY29tgg1jaS5nYWxheHkuY29tMB0G -A1UdDgQWBBQc0Va5pAmSTfcc4GplB5kartoL6TANBgkqhkiG9w0BAQsFAAOCAQEA -LWKXqUKbBddUSN4JDkUosAYkoNGo2yYMEGExEQ6VSimnyUSFypWfpR/XdIGqO9IL -Rbfe+5N7gkR+bfaTvMyvjpL9rOVwIBQBwV9t7T30xpsxmLRpDegWTn94sXUcmdwr -w0k4D3HgwznlFgew0nLlrfXWoKE44WxTKp/ClXoFp7k1FtxA7MrCHcgSWgGO1kni -G2+bf3UlzkLjLkUZ3HrqD3KQoXgHKTk7LHBWN7pNU6BbLBz+LTeTW7vqecxjGuvx -qWOP4MK0SNY/gQeOQd2DBCWFJFOVaKhgrNOrNMHHltWsgl6CGxG0SYNDNKCCmM9j -x+NGA4DfCOBb1yWmsYHQ/A== ------END CERTIFICATE----- diff --git a/demo/settings/certs/pulp_webserver.csr b/demo/settings/certs/pulp_webserver.csr deleted file mode 100644 index 451d078..0000000 --- a/demo/settings/certs/pulp_webserver.csr +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIDPjCCAiYCAQAwYTENMAsGA1UEAwwEcHVscDELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAk5DMRAwDgYDVQQHDAdSYWxlaWdoMRYwFAYDVQQKDA1SZWQgSGF0LCBJbmMu -MQwwCgYDVQQLDANwbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDb -pVD5xi/6dIGthfYCp4HFPgFEBBJtRdGooiq2Pd4b9umUjwU4WFJoXk0XgQO4WtZE -/YJ5iHuGe5v8AH6qfjC9fs8c+zvJVjqXRp+S2ynDvCPpPJ4CIABU1y2tYuVy+fzL -3mfljI0lph5/wUlWyVsfJwRCw3mKIlToIrTVyHqRka1oLP+lTNvi/p6rFD1p9a3i -dSNks54kBOVnSphDCf96ndozZ9DqFiiE4N9BqeflnjlL2upQhLorU2CvECBrqmnG -AIMQvuCbjUZ5fAFcuPPzil7JcLB0Lda3jJsUZLX2Ib42sHzWvczfc+b/0XzhXVhJ -vFFPsP97UrY4JqRGoOdPAgMBAAGggZcwgZQGCSqGSIb3DQEJDjGBhjCBgzAJBgNV -HRMEAjAAMAsGA1UdDwQEAwIF4DBpBgNVHREEYjBgggRwdWxwghBwdWxwLmV4YW1w -bGUuY29tggtjaS5wdWxwLmNvbYIGZ2FsYXh5ghJnYWxheHkuZXhhbXBsZS5jb22C -DWNpLmdhbGF4eS5jb22CDnB1bHAtZXBoZW1lcmFsMA0GCSqGSIb3DQEBCwUAA4IB -AQAIwgjet3bR+VCAitth0D37NuVFNepLnBukaS4eEpODbbocMrBfSSTWgoWyZcx3 -/hQBjcLFhubrxrPIDNRp0XOubJP25AnYOBurgpyF8aBeQch3itIaPrXCud9jxcI5 -vzcXcqe+X+hL106VSIM50X/0ljr8QBF0QPvHGO2LfUbDSkxvuO+8+k6MKdY5DWoY -/87EFtAsYaVY/Ln/gbYUxYnG1gQT0ar3mH6IC5hefOfmgNAx5AkXnfATR/TMpqVd -K1hTOEs9PcSssHq1TlmopuDYwjldlmEnVjRpydzIdBeez+bq2GQU9qgO5I6NdRul -xdtKfUMldG7oqw4K6k9bWE35 ------END CERTIFICATE REQUEST----- diff --git a/demo/settings/certs/pulp_webserver.key b/demo/settings/certs/pulp_webserver.key deleted file mode 100644 index 52e7c84..0000000 --- a/demo/settings/certs/pulp_webserver.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDbpVD5xi/6dIGt -hfYCp4HFPgFEBBJtRdGooiq2Pd4b9umUjwU4WFJoXk0XgQO4WtZE/YJ5iHuGe5v8 -AH6qfjC9fs8c+zvJVjqXRp+S2ynDvCPpPJ4CIABU1y2tYuVy+fzL3mfljI0lph5/ -wUlWyVsfJwRCw3mKIlToIrTVyHqRka1oLP+lTNvi/p6rFD1p9a3idSNks54kBOVn -SphDCf96ndozZ9DqFiiE4N9BqeflnjlL2upQhLorU2CvECBrqmnGAIMQvuCbjUZ5 -fAFcuPPzil7JcLB0Lda3jJsUZLX2Ib42sHzWvczfc+b/0XzhXVhJvFFPsP97UrY4 -JqRGoOdPAgMBAAECggEAXDzYvANzy4AYoweiD2xLjUqkGm2BVX0f9eqf+CcT+nde -kOHcpdRFSwnq4udtagJPf+pb2/CqOwf2scV9a9iRXEp/7GI+saax9SwVwASkTcvY -4s3vZuIMcZRpXKouw/FiLTTpOZckAP75SNohE758MyFd4zGWfSE8ub01Zp+JhU45 -2yOMVs0nJWDFNx5EeknCu32muWtAsuHhn8nFEVR854jIpiXXMIA4x4mGqtiJ7dsw -4FPGiMtbr2LGdlEt5fJ41FUzsQL/I4AB3yzbzBN/WD9kl6DxazHN9cEiemwG6yLq -1+BE0+RyWZkzw0KCwB8Z3msEinvfHmgvf+AYEJoGYQKBgQD7lJ0mMmN7glxVecdH -obfeXJIHRS930vXzqtpi6ta5XdOefCPRf3FHy/kOQmx3XAjPz5zqjtxhFwpfTPOo -v9F65mDi/lcG+PwPI/avvb5QHaDZGvqSOT3iRp4ZtiOr4En8iX/A5lzyS724dy6T -sdNfFI1DhcIyK22VNWNG4g3i4QKBgQDfgRaWIg0N2MgaAWfdXUlzoMQNwXv9qpsy -cU76ndetO3JvtFJaGMnzx4B8OLDV3PWZmUuFQWoYgiY6MXh+NZNzWhKHyZptaIzq -TdN2stvkwC5R5xheiExoqYOEsDseyiaZVPFmm3RvwSaKK8O0UBxUECZQr/2pI64/ -xEXM1NxALwKBgQC2DovSknBPdrze1iuR+MOYVKEQRc7eBXobBS7Yml0c83sqm2dI -OdN+Q2tOGF9p2N4/lFxXmV59nDDTWlqRaY5sQ78lRS0xTIkLY9kmwEayr14w/kcJ -/gZ8cuICdT5HKR4hdFdQ/uOQK6N1lDnOg8cUbUj07hzkNW0tpt7a8sddwQKBgQDW -83p6d1Mgrw17XUC797ztlH8ZT6zkwJC0CZ9Qjj9f3p1nawAMoGyRpJwgXBFODUyz -JWgpR0k3Ouxy6SMeFn6x4L2TlEON15A2wxSNwuSScnXZ1Sxtth3uRqEzGp4xNW+7 -5aKo8Pchh5x+JCr6nlUwT7vZu/h0E9nAgA41Ob59hwKBgQDYj1z2MusWB6/c8O1T -CQ3JYMzXqdYbHSozxXlBRA0N0oUuWzIMQXHfH+QQFxEjaQRdTaBLpgp6J4OXM1ia -pGdPGFnxHw63bkCjGDJ0UomoD8CHkdecL50ZLwqKtD8tGM+kscE1RO7txtObxC/P -/n2x41QA87SWlsbkCk9iVGrrVQ== ------END PRIVATE KEY----- diff --git a/demo/settings/certs/token_private_key.pem b/demo/settings/certs/token_private_key.pem deleted file mode 100644 index c9bf840..0000000 --- a/demo/settings/certs/token_private_key.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEINgU0fyK12OLF9IUfASKtEzE+xUYYfx2TZZF8c9n+I6voAoGCCqGSM49 -AwEHoUQDQgAERfGstBI/1n9aquPqaoI1jJ8JJrVZ6dSTNl7sRa3tKt1KBEfA1AIt -8qA5gZKo1sWQy2Rgr3Kzgrr2sCtpPlR3Qw== ------END EC PRIVATE KEY----- diff --git a/demo/settings/certs/token_public_key.pem b/demo/settings/certs/token_public_key.pem deleted file mode 100644 index e89ea22..0000000 --- a/demo/settings/certs/token_public_key.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERfGstBI/1n9aquPqaoI1jJ8JJrVZ -6dSTNl7sRa3tKt1KBEfA1AIt8qA5gZKo1sWQy2Rgr3Kzgrr2sCtpPlR3Qw== ------END PUBLIC KEY----- diff --git a/demo/settings/database_fields.symmetric.key b/demo/settings/database_fields.symmetric.key deleted file mode 100644 index f73cd1f..0000000 --- a/demo/settings/database_fields.symmetric.key +++ /dev/null @@ -1 +0,0 @@ -XlN6mYFXLtvJ30fQXvR96UxTNf+sNHbDmAERi7CWRf0= From 7036d74bde8d3d845147a54c7484a8118cbd3ce4 Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Fri, 3 Oct 2025 16:36:27 -0400 Subject: [PATCH 18/24] A few fixes to work with ansible tasks to setup demo Signed-off-by: Geoff Wilson --- Makefile | 51 +++-- demo/ansible/playbook.yml | 196 ++++++++++++++++++ .../assets/scripts/pulp-entrypoint-wrapper.sh | 22 ++ demo/assets/scripts/setup-signing-service.sh | 16 ++ demo/docker-compose.yml | 4 + setup-demo.sh | 4 +- 6 files changed, 277 insertions(+), 16 deletions(-) create mode 100644 demo/ansible/playbook.yml create mode 100755 demo/assets/scripts/pulp-entrypoint-wrapper.sh create mode 100755 demo/assets/scripts/setup-signing-service.sh diff --git a/Makefile b/Makefile index 9049dbe..3e37330 100644 --- a/Makefile +++ b/Makefile @@ -78,27 +78,27 @@ setup-network: setup-keys: @echo "Checking for Pulp encryption keys..." - @mkdir -p assets/certs assets/keys assets/nginx-conf - @if [ ! -f assets/certs/database_fields.symmetric.key ]; then \ + @mkdir -p demo/assets/certs demo/assets/keys demo/assets/nginx-conf + @if [ ! -f demo/assets/certs/database_fields.symmetric.key ]; then \ echo "Generating database encryption key..."; \ - openssl rand -base64 32 > assets/certs/database_fields.symmetric.key; \ + openssl rand -base64 32 > demo/assets/certs/database_fields.symmetric.key; \ echo "Database encryption key created."; \ else \ echo "Database encryption key already exists."; \ fi - @if [ ! -f assets/keys/container_auth_private_key.pem ]; then \ + @if [ ! -f demo/assets/keys/container_auth_private_key.pem ]; then \ echo "Generating container auth keys..."; \ - openssl ecparam -genkey -name secp256r1 -noout -out assets/keys/container_auth_private_key.pem; \ - openssl ec -in assets/keys/container_auth_private_key.pem -pubout -out assets/keys/container_auth_public_key.pem; \ + openssl ecparam -genkey -name secp256r1 -noout -out demo/assets/keys/container_auth_private_key.pem; \ + openssl ec -in demo/assets/keys/container_auth_private_key.pem -pubout -out demo/assets/keys/container_auth_public_key.pem; \ echo "Container auth keys created."; \ else \ echo "Container auth keys already exist."; \ fi - @mkdir -p assets/keys/gpg - @if [ ! -f assets/keys/gpg/public.key ] || [ ! -s assets/keys/gpg/public.key ]; then \ + @mkdir -p demo/assets/keys/gpg + @if [ ! -f demo/assets/keys/gpg/public.key ] || [ ! -s demo/assets/keys/gpg/public.key ]; then \ echo "Generating GPG signing keys..."; \ - rm -rf assets/keys/gpg/*; \ - chmod 700 assets/keys/gpg; \ + rm -rf demo/assets/keys/gpg/*; \ + chmod 700 demo/assets/keys/gpg; \ echo "Key-Type: RSA" > /tmp/gpg-batch-config; \ echo "Key-Length: 2048" >> /tmp/gpg-batch-config; \ echo "Name-Real: Demo Signing Service" >> /tmp/gpg-batch-config; \ @@ -106,8 +106,8 @@ setup-keys: echo "Expire-Date: 0" >> /tmp/gpg-batch-config; \ echo "%no-protection" >> /tmp/gpg-batch-config; \ echo "%commit" >> /tmp/gpg-batch-config; \ - GNUPGHOME=assets/keys/gpg gpg --batch --no-default-keyring --keyring assets/keys/gpg/pubring.kbx --gen-key /tmp/gpg-batch-config; \ - GNUPGHOME=assets/keys/gpg gpg --no-default-keyring --keyring assets/keys/gpg/pubring.kbx --armor --export > assets/keys/gpg/public.key; \ + GNUPGHOME=demo/assets/keys/gpg gpg --batch --no-default-keyring --keyring demo/assets/keys/gpg/pubring.kbx --gen-key /tmp/gpg-batch-config; \ + GNUPGHOME=demo/assets/keys/gpg gpg --no-default-keyring --keyring demo/assets/keys/gpg/pubring.kbx --armor --export > demo/assets/keys/gpg/public.key; \ rm /tmp/gpg-batch-config; \ echo "GPG signing keys created."; \ else \ @@ -116,5 +116,28 @@ setup-keys: .PHONY : setup-demo setup-demo: - @echo "Setting up complete demo environment..." - @./setup-demo.sh + @echo "Setting up demo environment..." + @docker run --rm \ + --network pulp-net \ + -v $(PWD)/demo/ansible:/ansible:ro \ + -v $(PWD)/demo/assets:/assets:ro \ + cytopia/ansible:latest \ + sh -c "pip3 install -q 'pulp-glue>=0.29.0' 'pulp-glue-deb>=0.3.0,<0.4' 2>&1 && \ + ansible-galaxy collection install pulp.squeezer 2>&1 | grep -v 'Installing' && \ + ansible-playbook -i localhost, /ansible/playbook.yml" + @echo "" + @echo "Demo Setup Complete" + @echo "===================" + @echo "" + @echo "Available repositories:" + @echo " - ext-small-repo (external): http://localhost:8000/pulp/content/ext-small-repo/" + @echo " - int-demo-packages (internal): http://localhost:8000/pulp/content/int-demo-packages/" + @echo "" + @echo "Pulp Manager sync commands:" + @echo " # Sync internal repositories:" + @echo " curl -X POST 'http://localhost:8080/v1/pulp_servers/2/sync_repos' -H 'Content-Type: application/json' -d '{\"max_runtime\": \"3600\", \"max_concurrent_syncs\": 5, \"regex_include\": \"int-.*\", \"regex_exclude\": \"\"}'" + @echo "" + @echo " # Sync external repositories:" + @echo " curl -X POST 'http://localhost:8080/v1/pulp_servers/2/sync_repos' -H 'Content-Type: application/json' -d '{\"max_runtime\": \"3600\", \"max_concurrent_syncs\": 5, \"regex_include\": \"ext-.*\", \"regex_exclude\": \"\"}'" + @echo "" + @echo "Monitor tasks: http://localhost:9181" diff --git a/demo/ansible/playbook.yml b/demo/ansible/playbook.yml new file mode 100644 index 0000000..b82113b --- /dev/null +++ b/demo/ansible/playbook.yml @@ -0,0 +1,196 @@ +--- +# Ansible playbook to set up Pulp demo environment +# Replaces setup-demo.sh with infrastructure-as-code approach + +- name: Setup Pulp Demo Environment + hosts: localhost + connection: local + gather_facts: false + + vars: + pulp_primary_url: "http://demo-pulp-primary-1" + pulp_secondary_url: "http://demo-pulp-secondary-1" + pulp_username: "admin" + pulp_password: "password" + package_file: "/assets/packages/hello_2.10-2_amd64.deb" + + tasks: + - name: Wait for Pulp Primary to be ready + pulp.squeezer.status: + pulp_url: "{{ pulp_primary_url }}" + username: "{{ pulp_username }}" + password: "{{ pulp_password }}" + register: primary_status + until: primary_status is not failed + retries: 30 + delay: 5 + + - name: Wait for Pulp Secondary to be ready + pulp.squeezer.status: + pulp_url: "{{ pulp_secondary_url }}" + username: "{{ pulp_username }}" + password: "{{ pulp_password }}" + register: secondary_status + until: secondary_status is not failed + retries: 30 + delay: 5 + + - name: Create int-demo-packages repository on primary + pulp.squeezer.deb_repository: + pulp_url: "{{ pulp_primary_url }}" + username: "{{ pulp_username }}" + password: "{{ pulp_password }}" + name: "int-demo-packages" + description: "Internal demo repository\n base_url: int-demo-packages" + state: present + register: int_repo + + - name: Create ext-small-repo repository on primary + pulp.squeezer.deb_repository: + pulp_url: "{{ pulp_primary_url }}" + username: "{{ pulp_username }}" + password: "{{ pulp_password }}" + name: "ext-small-repo" + description: "External demo repository\n base_url: ext-small-repo" + state: present + register: ext_repo + + - name: Get int-demo-packages repository details + pulp.squeezer.deb_repository: + pulp_url: "{{ pulp_primary_url }}" + username: "{{ pulp_username }}" + password: "{{ pulp_password }}" + name: "int-demo-packages" + state: present + register: int_repo_info + + - name: Check if demo package already exists + uri: + url: "{{ pulp_primary_url }}{{ int_repo_info.repository.latest_version_href }}content/?limit=100" + method: GET + force_basic_auth: true + url_username: "{{ pulp_username }}" + url_password: "{{ pulp_password }}" + status_code: [200, 404] + register: repo_content + when: int_repo_info.repository.latest_version_href is defined and int_repo_info.repository.latest_version_href != None + + - name: Set package exists fact + set_fact: + package_exists: "{{ repo_content.json.results | selectattr('relative_path', 'defined') | selectattr('relative_path', 'search', 'hello') | list | length > 0 }}" + when: repo_content is defined and repo_content.json is defined and repo_content.status == 200 + + - name: Upload demo package + uri: + url: "{{ pulp_primary_url }}/pulp/api/v3/content/deb/packages/" + method: POST + body_format: form-multipart + body: + file: + filename: hello_2.10-2_amd64.deb + content: "{{ lookup('file', package_file) | b64encode }}" + mime_type: application/octet-stream + force_basic_auth: true + url_username: "{{ pulp_username }}" + url_password: "{{ pulp_password }}" + status_code: [201, 202] + register: upload_result + when: package_exists is not defined or not package_exists + + - name: Wait for upload task to complete + pulp.squeezer.task: + pulp_url: "{{ pulp_primary_url }}" + username: "{{ pulp_username }}" + password: "{{ pulp_password }}" + href: "{{ upload_result.json.task }}" + register: upload_task + until: upload_task.task.state == "completed" + retries: 30 + delay: 2 + when: upload_result is changed + + - name: Get content href from task + set_fact: + content_href: "{{ upload_task.task.created_resources[0] }}" + when: upload_result is changed + + - name: Add content to int-demo-packages repository + pulp.squeezer.deb_repository: + pulp_url: "{{ pulp_primary_url }}" + username: "{{ pulp_username }}" + password: "{{ pulp_password }}" + name: "int-demo-packages" + state: present + content_units: + - "{{ content_href }}" + when: upload_result is changed + + - name: Create publication for int-demo-packages + uri: + url: "{{ pulp_primary_url }}/pulp/api/v3/publications/deb/apt/" + method: POST + body_format: json + body: + repository: "{{ int_repo_info.repository.pulp_href }}" + simple: true + force_basic_auth: true + url_username: "{{ pulp_username }}" + url_password: "{{ pulp_password }}" + status_code: [201, 202] + register: int_publication_create + + - name: Wait for publication task + uri: + url: "{{ pulp_primary_url }}{{ int_publication_create.json.task }}" + method: GET + force_basic_auth: true + url_username: "{{ pulp_username }}" + url_password: "{{ pulp_password }}" + register: publication_task + until: publication_task.json.state == "completed" + retries: 30 + delay: 2 + when: int_publication_create.json.task is defined + + - name: Get publication href + set_fact: + int_publication_href: "{{ publication_task.json.created_resources[0] }}" + when: publication_task.json is defined + + - name: Check if distribution exists + uri: + url: "{{ pulp_primary_url }}/pulp/api/v3/distributions/deb/apt/?name=int-demo-packages" + method: GET + force_basic_auth: true + url_username: "{{ pulp_username }}" + url_password: "{{ pulp_password }}" + register: dist_check + + - name: Update existing distribution + uri: + url: "{{ pulp_primary_url }}{{ dist_check.json.results[0].pulp_href }}" + method: PATCH + body_format: json + body: + publication: "{{ int_publication_href }}" + force_basic_auth: true + url_username: "{{ pulp_username }}" + url_password: "{{ pulp_password }}" + status_code: [200, 202] + when: dist_check.json.count > 0 + + - name: Create new distribution + uri: + url: "{{ pulp_primary_url }}/pulp/api/v3/distributions/deb/apt/" + method: POST + body_format: json + body: + name: "int-demo-packages" + base_path: "int-demo-packages" + publication: "{{ int_publication_href }}" + force_basic_auth: true + url_username: "{{ pulp_username }}" + url_password: "{{ pulp_password }}" + status_code: [201, 202] + when: dist_check.json.count == 0 + diff --git a/demo/assets/scripts/pulp-entrypoint-wrapper.sh b/demo/assets/scripts/pulp-entrypoint-wrapper.sh new file mode 100755 index 0000000..269a891 --- /dev/null +++ b/demo/assets/scripts/pulp-entrypoint-wrapper.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Wrapper script that runs Pulp's normal entrypoint, then sets up signing service + +# Start Pulp in the background using its normal entrypoint +/usr/local/bin/pulp-api & +PULP_PID=$! + +# Wait for Pulp to be ready +echo "Waiting for Pulp to start..." +for i in $(seq 1 60); do + if curl -s http://localhost/pulp/api/v3/status/ > /dev/null 2>&1; then + echo "Pulp is ready" + break + fi + sleep 2 +done + +# Run signing service setup +/opt/scripts/setup-signing-service.sh + +# Wait for Pulp process +wait $PULP_PID diff --git a/demo/assets/scripts/setup-signing-service.sh b/demo/assets/scripts/setup-signing-service.sh new file mode 100755 index 0000000..033ce57 --- /dev/null +++ b/demo/assets/scripts/setup-signing-service.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# Registers the signing service if it doesn't already exist + +existing=$(pulpcore-manager shell -c "from pulpcore.app.models import SigningService; print(SigningService.objects.filter(name='deb_signing_service').exists())" 2>/dev/null) + +if [ "$existing" != "True" ]; then + key_id=$(GNUPGHOME=/opt/gpg gpg --list-secret-keys --with-colons 2>/dev/null | grep "^sec:" | cut -d: -f5 | head -1) + if [ -n "$key_id" ]; then + pulpcore-manager add-signing-service deb_signing_service /opt/scripts/deb_sign.sh "$key_id" --gnupghome /opt/gpg 2>/dev/null + echo "Signing service 'deb_signing_service' created" + else + echo "No GPG key found, skipping signing service setup" + fi +else + echo "Signing service 'deb_signing_service' already exists" +fi diff --git a/demo/docker-compose.yml b/demo/docker-compose.yml index e89858d..016e716 100644 --- a/demo/docker-compose.yml +++ b/demo/docker-compose.yml @@ -15,6 +15,8 @@ services: - "/dev/fuse" networks: - pulp-net + entrypoint: ["/bin/bash", "-c"] + command: ["sleep 10 && /opt/scripts/setup-signing-service.sh & /usr/local/bin/pulp-api"] pulp-secondary: image: pulp/pulp:stable @@ -32,6 +34,8 @@ services: - "/dev/fuse" networks: - pulp-net + entrypoint: ["/bin/bash", "-c"] + command: ["sleep 10 && /opt/scripts/setup-signing-service.sh & /usr/local/bin/pulp-api"] # Pulp Manager services mariadb: diff --git a/setup-demo.sh b/setup-demo.sh index 0dc2e84..3d3a185 100755 --- a/setup-demo.sh +++ b/setup-demo.sh @@ -122,8 +122,8 @@ register_signing_service() { echo "" echo "Setting up signing services..." echo "==============================" -register_signing_service "docker-pulp-primary-1" "Pulp Primary" -register_signing_service "docker-pulp-secondary-1" "Pulp Secondary" +register_signing_service "demo-pulp-primary-1" "Pulp Primary" +register_signing_service "demo-pulp-secondary-1" "Pulp Secondary" echo "" echo "Step 1: Getting Repository References" From 7597fc17596bb8c917f3f02022ee1dd99046c63d Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Fri, 3 Oct 2025 16:39:14 -0400 Subject: [PATCH 19/24] Remove the setup-demo script, using ansible instead Signed-off-by: Geoff Wilson --- setup-demo.sh | 257 -------------------------------------------------- 1 file changed, 257 deletions(-) delete mode 100755 setup-demo.sh diff --git a/setup-demo.sh b/setup-demo.sh deleted file mode 100755 index 3d3a185..0000000 --- a/setup-demo.sh +++ /dev/null @@ -1,257 +0,0 @@ -#!/bin/bash - -set -e - -PULP_PRIMARY="http://localhost:8000" -PULP_SECONDARY="http://localhost:8001" -PULP_USER="admin" -PULP_PASS="password" - -echo "Setting up Pulp Demo Environment" -echo "=================================" - -# Function to check if a server is running and accessible -check_server_running() { - local server="$1" - local server_name="$2" - - echo "Checking if $server_name is running..." - - if ! curl -s --connect-timeout 5 "$server/pulp/api/v3/status/" > /dev/null 2>&1; then - echo "ERROR: $server_name at $server is not accessible!" - echo "" - echo "To fix this issue:" - echo " 1. Start the Pulp cluster: make run-cluster" - echo " 2. Wait for services to be healthy (may take 1-2 minutes)" - echo " 3. Then run: make setup-demo" - echo "" - return 1 - fi - - echo "$server_name is running and accessible" - return 0 -} - -# Function to wait for task completion -wait_for_task() { - local task_href="$1" - local server="$2" - echo " Waiting for task to complete..." - - while true; do - local task_result=$(curl -s -u $PULP_USER:$PULP_PASS "$server$task_href") - local state=$(echo "$task_result" | jq -r '.state') - - case "$state" in - "completed") - echo " Task completed successfully" - return 0 - ;; - "failed") - echo " Task failed!" - echo " Error: $(echo "$task_result" | jq -r '.error.description // .error')" - return 1 - ;; - "running"|"waiting") - echo " Task $state, waiting..." - sleep 2 - ;; - *) - echo " Task in state: $state, waiting..." - sleep 2 - ;; - esac - done -} - -echo "" -echo "Checking server connectivity..." -check_server_running "$PULP_PRIMARY" "Pulp Primary" -check_server_running "$PULP_SECONDARY" "Pulp Secondary" - -# Function to register signing service in Pulp -assign_signing_service_to_repo() { - local server="$1" - local repo_href="$2" - local repo_name="$3" - - # Get the signing service href for this server - local signing_service_href=$(curl -s -u $PULP_USER:$PULP_PASS "$server/pulp/api/v3/signing-services/?name=deb_signing_service" | jq -r '.results[0].pulp_href') - - if [ -n "$signing_service_href" ] && [ "$signing_service_href" != "null" ]; then - echo " Assigning signing service to $repo_name..." - curl -s -u $PULP_USER:$PULP_PASS -X PATCH "$server$repo_href" \ - -H "Content-Type: application/json" \ - -d "{\"signing_service\": \"$signing_service_href\"}" > /dev/null - echo " Signing service assigned: $signing_service_href" - fi -} - -register_signing_service() { - local container_name="$1" - local server_name="$2" - - echo " Checking if signing service exists on $server_name..." - - # Check if signing service already exists in the database - existing=$(docker exec $container_name bash -c "pulpcore-manager shell -c \"from pulpcore.app.models import SigningService; print(SigningService.objects.filter(name='deb_signing_service').exists())\"" 2>/dev/null) - - if [ "$existing" = "True" ]; then - echo " Signing service 'deb_signing_service' already exists on $server_name" - else - echo " Creating signing service 'deb_signing_service' on $server_name..." - - # Get the GPG key ID from the mounted keyring - key_id=$(docker exec $container_name bash -c "GNUPGHOME=/opt/gpg gpg --list-secret-keys --with-colons 2>/dev/null | grep '^sec:' | cut -d: -f5 | head -1") - - if [ -n "$key_id" ]; then - # Add the signing service using the management command - docker exec $container_name bash -c "pulpcore-manager add-signing-service \ - 'deb_signing_service' \ - '/opt/scripts/deb_sign.sh' \ - '$key_id' \ - --gnupghome /opt/gpg" 2>/dev/null && \ - echo " Signing service created successfully on $server_name" || \ - echo " Failed to create signing service on $server_name" - else - echo " No GPG key found in /opt/gpg on $server_name" - fi - fi -} - -echo "" -echo "Setting up signing services..." -echo "==============================" -register_signing_service "demo-pulp-primary-1" "Pulp Primary" -register_signing_service "demo-pulp-secondary-1" "Pulp Secondary" - -echo "" -echo "Step 1: Getting Repository References" -echo "=====================================" - -# Get repository hrefs (assumes repos already exist in Pulp) -echo " Getting int-demo-packages repository..." -REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/?name=int-demo-packages") -INT_REPO_HREF=$(echo "$REPO_RESPONSE" | jq -r '.results[0].pulp_href') - -if [ -z "$INT_REPO_HREF" ] || [ "$INT_REPO_HREF" = "null" ]; then - echo " ERROR: Repository 'int-demo-packages' not found on primary server" - echo " Please create repositories manually or via Pulp Manager before running this script" - exit 1 -fi -echo " Found: $INT_REPO_HREF" - -echo " Getting ext-small-repo repository..." -REPO_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY/pulp/api/v3/repositories/deb/apt/?name=ext-small-repo") -EXT_REPO_HREF=$(echo "$REPO_RESPONSE" | jq -r '.results[0].pulp_href') - -if [ -z "$EXT_REPO_HREF" ] || [ "$EXT_REPO_HREF" = "null" ]; then - echo " ERROR: Repository 'ext-small-repo' not found on primary server" - exit 1 -fi -echo " Found: $EXT_REPO_HREF" - -echo "" -echo "Step 2: Uploading Demo Package to Internal Repository" -echo "=====================================================" - -# Check if package already exists in repository -# First get the latest version href -LATEST_VERSION=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY${INT_REPO_HREF}versions/?limit=1&ordering=-number" | jq -r '.results[0].pulp_href' 2>/dev/null) -if [ -n "$LATEST_VERSION" ] && [ "$LATEST_VERSION" != "null" ]; then - INT_REPO_CONTENT=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY${LATEST_VERSION}content/?limit=100") - HELLO_PKG_EXISTS=$(echo "$INT_REPO_CONTENT" | jq -r '.results[] | select(.relative_path and (.relative_path | contains("hello"))) | .pulp_href' 2>/dev/null | head -n1) -else - HELLO_PKG_EXISTS="" -fi - -if [ -n "$HELLO_PKG_EXISTS" ] && [ "$HELLO_PKG_EXISTS" != "null" ]; then - echo " Demo package already exists in repository" -else - echo " Uploading demo package..." - CONTENT_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/content/deb/packages/" \ - -H "Content-Type: multipart/form-data" \ - -F "file=@demo/assets/packages/hello_2.10-2_amd64.deb") - - UPLOAD_TASK_HREF=$(echo "$CONTENT_RESPONSE" | jq -r '.task') - wait_for_task "$UPLOAD_TASK_HREF" "$PULP_PRIMARY" - - # Get content href from completed task - TASK_RESULT=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY$UPLOAD_TASK_HREF") - CONTENT_HREF=$(echo "$TASK_RESULT" | jq -r '.created_resources[0]') - echo " Content created: $CONTENT_HREF" - - # Add content to repository - echo " Adding content to repository..." - MODIFY_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY${INT_REPO_HREF}modify/" \ - -H "Content-Type: application/json" \ - -d "{\"add_content_units\": [\"$CONTENT_HREF\"]}") - - MODIFY_TASK_HREF=$(echo "$MODIFY_RESPONSE" | jq -r '.task') - wait_for_task "$MODIFY_TASK_HREF" "$PULP_PRIMARY" -fi - -echo "" -echo "Step 3: Creating Publications and Distributions" -echo "===============================================" - -# Create publication for internal repository -echo " Creating publication for int-demo-packages..." -INT_PUB_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/publications/deb/apt/" \ - -H "Content-Type: application/json" \ - -d "{\"repository\": \"$INT_REPO_HREF\", \"simple\": true}") - -if echo "$INT_PUB_RESPONSE" | jq -e '.task' > /dev/null; then - INT_PUB_TASK=$(echo "$INT_PUB_RESPONSE" | jq -r '.task') - wait_for_task "$INT_PUB_TASK" "$PULP_PRIMARY" - - # Get publication href - PUB_TASK_RESULT=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY$INT_PUB_TASK") - INT_PUB_HREF=$(echo "$PUB_TASK_RESULT" | jq -r '.created_resources[0]') - echo " Publication created: $INT_PUB_HREF" -fi - -# Create or update distribution for internal repository -DIST_RESPONSE=$(curl -s -u $PULP_USER:$PULP_PASS "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/?name=int-demo-packages") -DIST_EXISTS=$(echo "$DIST_RESPONSE" | jq -r '.count') - -if [ "$DIST_EXISTS" -gt 0 ]; then - echo " Updating distribution for int-demo-packages..." - DIST_HREF=$(echo "$DIST_RESPONSE" | jq -r '.results[0].pulp_href') - DIST_UPDATE=$(curl -s -u $PULP_USER:$PULP_PASS -X PATCH "$PULP_PRIMARY$DIST_HREF" \ - -H "Content-Type: application/json" \ - -d "{\"publication\": \"$INT_PUB_HREF\"}") - - if echo "$DIST_UPDATE" | jq -e '.task' > /dev/null; then - DIST_TASK=$(echo "$DIST_UPDATE" | jq -r '.task') - wait_for_task "$DIST_TASK" "$PULP_PRIMARY" - fi -else - echo " Creating distribution for int-demo-packages..." - DIST_CREATE=$(curl -s -u $PULP_USER:$PULP_PASS -X POST "$PULP_PRIMARY/pulp/api/v3/distributions/deb/apt/" \ - -H "Content-Type: application/json" \ - -d "{\"name\": \"int-demo-packages\", \"base_path\": \"int-demo-packages\", \"publication\": \"$INT_PUB_HREF\"}") - - if echo "$DIST_CREATE" | jq -e '.task' > /dev/null; then - DIST_TASK=$(echo "$DIST_CREATE" | jq -r '.task') - wait_for_task "$DIST_TASK" "$PULP_PRIMARY" - fi -fi -echo " Distribution ready for int-demo-packages" - -echo "" -echo " Demo Setup Complete!" -echo "======================" -echo "" -echo "Available repositories:" -echo " ext-small-repo (external): $PULP_PRIMARY/pulp/content/ext-small-repo/" -echo " int-demo-packages (internal): $PULP_PRIMARY/pulp/content/int-demo-packages/" -echo "" -echo "Pulp Manager sync commands:" -echo " # Sync internal repositories:" -echo " curl -X POST 'http://localhost:8080/v1/pulp_servers/2/sync_repos' -H 'Content-Type: application/json' -d '{\"max_runtime\": \"3600\", \"max_concurrent_syncs\": 5, \"regex_include\": \"int-.*\", \"regex_exclude\": \"\"}'" -echo "" -echo " # Sync external repositories:" -echo " curl -X POST 'http://localhost:8080/v1/pulp_servers/2/sync_repos' -H 'Content-Type: application/json' -d '{\"max_runtime\": \"3600\", \"max_concurrent_syncs\": 5, \"regex_include\": \"ext-.*\", \"regex_exclude\": \"\"}'" -echo "" -echo "Monitor tasks: http://localhost:9181" \ No newline at end of file From 6d02d166dad1000408b5a91e77c9273cede8d254 Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Mon, 6 Oct 2025 14:35:47 -0400 Subject: [PATCH 20/24] remove demo key material Signed-off-by: Geoff Wilson --- .../certs/database_fields.symmetric.key | 1 - demo/assets/certs/pulp_webserver.crt | 23 ------------- demo/assets/certs/pulp_webserver.csr | 20 ----------- demo/assets/certs/pulp_webserver.key | 28 --------------- demo/assets/certs/token_private_key.pem | 5 --- demo/assets/certs/token_public_key.pem | 4 --- .../keys/container_auth_private_key.pem | 5 --- .../assets/keys/container_auth_public_key.pem | 4 --- ...6957F72E4E766D9F0DA17C7176329BF3E5467B.rev | 32 ------------------ ...4746844C0C150A7CD566D9784172C3BEC0ED90.key | 28 --------------- demo/assets/keys/gpg/public.key | 18 ---------- demo/assets/keys/gpg/pubring.kbx | Bin 843 -> 0 bytes demo/assets/keys/gpg/pubring.kbx~ | Bin 32 -> 0 bytes demo/assets/keys/gpg/trustdb.gpg | Bin 1280 -> 0 bytes 14 files changed, 168 deletions(-) delete mode 100644 demo/assets/certs/database_fields.symmetric.key delete mode 100644 demo/assets/certs/pulp_webserver.crt delete mode 100644 demo/assets/certs/pulp_webserver.csr delete mode 100644 demo/assets/certs/pulp_webserver.key delete mode 100644 demo/assets/certs/token_private_key.pem delete mode 100644 demo/assets/certs/token_public_key.pem delete mode 100644 demo/assets/keys/container_auth_private_key.pem delete mode 100644 demo/assets/keys/container_auth_public_key.pem delete mode 100644 demo/assets/keys/gpg/openpgp-revocs.d/086957F72E4E766D9F0DA17C7176329BF3E5467B.rev delete mode 100644 demo/assets/keys/gpg/private-keys-v1.d/AB4746844C0C150A7CD566D9784172C3BEC0ED90.key delete mode 100644 demo/assets/keys/gpg/public.key delete mode 100644 demo/assets/keys/gpg/pubring.kbx delete mode 100644 demo/assets/keys/gpg/pubring.kbx~ delete mode 100644 demo/assets/keys/gpg/trustdb.gpg diff --git a/demo/assets/certs/database_fields.symmetric.key b/demo/assets/certs/database_fields.symmetric.key deleted file mode 100644 index f73cd1f..0000000 --- a/demo/assets/certs/database_fields.symmetric.key +++ /dev/null @@ -1 +0,0 @@ -XlN6mYFXLtvJ30fQXvR96UxTNf+sNHbDmAERi7CWRf0= diff --git a/demo/assets/certs/pulp_webserver.crt b/demo/assets/certs/pulp_webserver.crt deleted file mode 100644 index 4c36051..0000000 --- a/demo/assets/certs/pulp_webserver.crt +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDzDCCArSgAwIBAgIURAIunfFsxKyouqd597QLlcbTIcowDQYJKoZIhvcNAQEL -BQAwYTENMAsGA1UEAwwEcHVscDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5DMRAw -DgYDVQQHDAdSYWxlaWdoMRYwFAYDVQQKDA1SZWQgSGF0LCBJbmMuMQwwCgYDVQQL -DANwbnQwHhcNMjUwOTI5MTc0MDI1WhcNMjYwOTI5MTc0MDI1WjBhMQ0wCwYDVQQD -DARwdWxwMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxEDAOBgNVBAcMB1JhbGVp -Z2gxFjAUBgNVBAoMDVJlZCBIYXQsIEluYy4xDDAKBgNVBAsMA3BudDCCASIwDQYJ -KoZIhvcNAQEBBQADggEPADCCAQoCggEBAN1Ak3WZvs8aJPpTkp78Iq5d8N0H5MDh -WdkNX7GFxti7SJJZ4IkwjuCzMXOSNMWBxCcZCUCsrl36WgjNDxOC6ljBq7zj9eIZ -U3V+BsohuStgwBb4Jrw7FRVZTLEn4JIuv56wQGh6356p/ABgYQChF8aYLjowZ+sP -WtUd29osaRmc01h62vOUvHztyGYliKUPQKUjKxjhudqBQ5LE6ZMX7Unr7vpL1Zkw -Z2WIzvdneesF6GKYjs5262+PSl5aOg0Nbl1SHUK8UagkAlEcq3BhJHpEZJIsanCx -bcoBf5i6iS4Z/19MscfcWKXtTLtb45u5uPq+Abhpxl3j55ElwwQZiTUCAwEAAaN8 -MHowWQYDVR0RBFIwUIIEcHVscIIQcHVscC5leGFtcGxlLmNvbYILY2kucHVscC5j -b22CBmdhbGF4eYISZ2FsYXh5LmV4YW1wbGUuY29tgg1jaS5nYWxheHkuY29tMB0G -A1UdDgQWBBTY1ep9PgMxX1j0G3vEo8dtOCGLFDANBgkqhkiG9w0BAQsFAAOCAQEA -lC3wrjKbD00hQHCy0DTPgWCLo1RwmsB3ogL00A8uaGgfocXx7BPCCcUKLAKfpSzo -+Z8BpeqF0u8ZCatqeE9nDvytbEh6775L925OUv/UE9luE2nyh889QNzFhB2oAOM+ -hXK4An8P8uTMA24EhcYCoCxzvftZ6DdRqGsDSD6oJzUhQhiYR2OctgVFxYNopT7u -gJP38mI2tguiX+QdFsrudCMZzrmUdpbyG/60PNPnMYGYhqb6F0SdL6Yq0KY76qUH -HgBId3tGh4lZIfYbixg/IySRmGNHkLl9FHxbUpaUI27kIIoMjb7PWWbnPOo6Imqx -nbm7O6uq3ZNDypnmcxP5qg== ------END CERTIFICATE----- diff --git a/demo/assets/certs/pulp_webserver.csr b/demo/assets/certs/pulp_webserver.csr deleted file mode 100644 index fa747ae..0000000 --- a/demo/assets/certs/pulp_webserver.csr +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIDPjCCAiYCAQAwYTENMAsGA1UEAwwEcHVscDELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAk5DMRAwDgYDVQQHDAdSYWxlaWdoMRYwFAYDVQQKDA1SZWQgSGF0LCBJbmMu -MQwwCgYDVQQLDANwbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDd -QJN1mb7PGiT6U5Ke/CKuXfDdB+TA4VnZDV+xhcbYu0iSWeCJMI7gszFzkjTFgcQn -GQlArK5d+loIzQ8TgupYwau84/XiGVN1fgbKIbkrYMAW+Ca8OxUVWUyxJ+CSLr+e -sEBoet+eqfwAYGEAoRfGmC46MGfrD1rVHdvaLGkZnNNYetrzlLx87chmJYilD0Cl -IysY4bnagUOSxOmTF+1J6+76S9WZMGdliM73Z3nrBehimI7Odutvj0peWjoNDW5d -Uh1CvFGoJAJRHKtwYSR6RGSSLGpwsW3KAX+YuokuGf9fTLHH3Fil7Uy7W+Obubj6 -vgG4acZd4+eRJcMEGYk1AgMBAAGggZcwgZQGCSqGSIb3DQEJDjGBhjCBgzAJBgNV -HRMEAjAAMAsGA1UdDwQEAwIF4DBpBgNVHREEYjBgggRwdWxwghBwdWxwLmV4YW1w -bGUuY29tggtjaS5wdWxwLmNvbYIGZ2FsYXh5ghJnYWxheHkuZXhhbXBsZS5jb22C -DWNpLmdhbGF4eS5jb22CDnB1bHAtZXBoZW1lcmFsMA0GCSqGSIb3DQEBCwUAA4IB -AQDISjZSZRXm1jdM3gZiajNJ3wqudlSezbPT0TXqdJLUnoJXkpIzmDma4faqJIit -4SMrvFH1TIeyTuWQDcEAGv+Eq0epsWrHR/FiqlKL1l0G1qk7DEbXQAbsE5mBauLX -JxT69pS5toD0zN9XfVm8rvzyNwigNurlbtrI7ycu+E3lQwxEjNrA+XR4rsbfzNx+ -/WyRmJUZBMS4432uibSW2MzXmaKaMLOiPmmTSpXwMW88BT2IxYirEk5kDedZ1LTA -1U6v0q8wKtIpzjmuqDjW2vq1q+GKpAmOS5faXIUUeipGuAaeKIf7rrmljFKE9VQe -ErRx0c/skNjVxTQo+T24Qep5 ------END CERTIFICATE REQUEST----- diff --git a/demo/assets/certs/pulp_webserver.key b/demo/assets/certs/pulp_webserver.key deleted file mode 100644 index 411d8e6..0000000 --- a/demo/assets/certs/pulp_webserver.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDdQJN1mb7PGiT6 -U5Ke/CKuXfDdB+TA4VnZDV+xhcbYu0iSWeCJMI7gszFzkjTFgcQnGQlArK5d+loI -zQ8TgupYwau84/XiGVN1fgbKIbkrYMAW+Ca8OxUVWUyxJ+CSLr+esEBoet+eqfwA -YGEAoRfGmC46MGfrD1rVHdvaLGkZnNNYetrzlLx87chmJYilD0ClIysY4bnagUOS -xOmTF+1J6+76S9WZMGdliM73Z3nrBehimI7Odutvj0peWjoNDW5dUh1CvFGoJAJR -HKtwYSR6RGSSLGpwsW3KAX+YuokuGf9fTLHH3Fil7Uy7W+Obubj6vgG4acZd4+eR -JcMEGYk1AgMBAAECggEABqljVzrgFyHBI8Vg6IcMb0YWUr1iWpleaG3h5/kwbcWj -z35Dx7Wt05+pmUJ5csnvs0Kqd+dLH7rCO7oa3lpGfpIkPt15QYvEKsk24J6n0eHJ -ntdtaST5Q0mLSNk7xoMAU4GYitzKP198XjGIsilnixv5ZlifRGFTuY9612SXbIUx -LdXV6mazuTP+bxFiIYBeLOkG+9qtQanSrPcdyxXTSU1Ve9F8194yqHpQx37c19vY -PP2ffWnwcz9O8IbRW9J9uzAX6y8/H5hegROTOszoPX8UBytwebPDlWBLNkg46kBQ -qzC32sRIQUF080SLr1nm37VFZe7gwnzG1xYIkdc2kQKBgQD+XjIL89QHuGFV6B2U -s+fUj1m/ODlzF6PfgcVCKw9B0sp2CeHrQn7iyM7Xt0BFrV88JDiCQgrulOU1cqbl -uoKhYtKdPJ5jl/uwKkl3/kPkHJrj8rlEWQKo3l+v9Hc8W/v5aZK7F5zw+4qoUraB -uxRJiHVWIksqvWfS5bIPuGeqJQKBgQDeq/zATuiNFML7reUHQUxPFew0RNtgslAx -6zUzdjl3hbXaOISOFU0yZvRg0I/8dVkUHjgHOX9u5k6YGrqDGzJ5/Vewo3bbHCBk -58u74OPvhVoqizcgoBF17Qrq6JwDbS+UkhJv7Y/vbbTmajQQlFF3q5unkg/bouFL -7BvhwdjN0QKBgQCgyUX1TDEQmDnepZRdNMMsF1jxiEa4O484br0TsEg6oVWc+240 -2Zl/HNOyg4E7CfYS/ApEPB7Q5IlmGYzp1dVQ0jizb2fnKGDN4E0EblLX1EUMJZd6 -XpFR0Q7HGE5udu51n4hCfxCTO01QTMhUhL60JG+W/KJq58LDCrJdQYE1iQKBgFSt -Ho6a80BDNuqydDfQEw64DXzK+onJBUoWYcLSIIRdKoxzlaTaYOLb1+7BISAmF9vY -qgHFUbqAhj69W1PkEcvmFWSspNQp2//DTeyCVuuM0H8BNdOIS1uG6vHtxvZenQto -iO5bbrLkCzjcBjSP0nMppSWSG8mwJPDUNr4hEyshAoGBAMSO/r+uCsBaJnbAZw2o -YGDmYL9iuP59T8GaGrAScA0lFYr1OAxTRPdEdW2xJEWJ1wPcucMD8zXzbKJ+Zuvu -ct9JRVn2gxfLpXteUpfaj3u3MrTVP3F5i9x9HWT5a0qCwlkOdcE+PJhLpEYYtbaZ -thoi2K72DfZJGbEv7JkRBNDB ------END PRIVATE KEY----- diff --git a/demo/assets/certs/token_private_key.pem b/demo/assets/certs/token_private_key.pem deleted file mode 100644 index 15ed860..0000000 --- a/demo/assets/certs/token_private_key.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIKt5HoJotGmry4eug7eZZnl+glYHe66UnEpVJ2E3kKZqoAoGCCqGSM49 -AwEHoUQDQgAE59WySChJ+/C01aYCq3a+0bt+QmsjeU60adfCSpjFKVVgZXqqgWHi -+0PCM/+uBBPCVqlSxgpdm11XPUApEComZw== ------END EC PRIVATE KEY----- diff --git a/demo/assets/certs/token_public_key.pem b/demo/assets/certs/token_public_key.pem deleted file mode 100644 index 9318e84..0000000 --- a/demo/assets/certs/token_public_key.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE59WySChJ+/C01aYCq3a+0bt+Qmsj -eU60adfCSpjFKVVgZXqqgWHi+0PCM/+uBBPCVqlSxgpdm11XPUApEComZw== ------END PUBLIC KEY----- diff --git a/demo/assets/keys/container_auth_private_key.pem b/demo/assets/keys/container_auth_private_key.pem deleted file mode 100644 index a36d6ec..0000000 --- a/demo/assets/keys/container_auth_private_key.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEINq1lvEDM9pLdlCp8TisKOCKis4XrvWOTQj50oyIx01loAoGCCqGSM49 -AwEHoUQDQgAEWy5LFfvJ/3xV3r1qmZUOkhstQNuVAYYfPg3e8CWgIas0YCmCQE8L -WnvoHlsEjKlMHblSElN7xnA/2PFzut1QSQ== ------END EC PRIVATE KEY----- diff --git a/demo/assets/keys/container_auth_public_key.pem b/demo/assets/keys/container_auth_public_key.pem deleted file mode 100644 index 50b65c9..0000000 --- a/demo/assets/keys/container_auth_public_key.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWy5LFfvJ/3xV3r1qmZUOkhstQNuV -AYYfPg3e8CWgIas0YCmCQE8LWnvoHlsEjKlMHblSElN7xnA/2PFzut1QSQ== ------END PUBLIC KEY----- diff --git a/demo/assets/keys/gpg/openpgp-revocs.d/086957F72E4E766D9F0DA17C7176329BF3E5467B.rev b/demo/assets/keys/gpg/openpgp-revocs.d/086957F72E4E766D9F0DA17C7176329BF3E5467B.rev deleted file mode 100644 index 5ba4998..0000000 --- a/demo/assets/keys/gpg/openpgp-revocs.d/086957F72E4E766D9F0DA17C7176329BF3E5467B.rev +++ /dev/null @@ -1,32 +0,0 @@ -This is a revocation certificate for the OpenPGP key: - -pub rsa2048 2025-09-15 [SCEAR] - 086957F72E4E766D9F0DA17C7176329BF3E5467B -uid Demo Signing Service - -A revocation certificate is a kind of "kill switch" to publicly -declare that a key shall not anymore be used. It is not possible -to retract such a revocation certificate once it has been published. - -Use it to revoke this key in case of a compromise or loss of -the secret key. However, if the secret key is still accessible, -it is better to generate a new revocation certificate and give -a reason for the revocation. For details see the description of -of the gpg command "--generate-revocation" in the GnuPG manual. - -To avoid an accidental use of this file, a colon has been inserted -before the 5 dashes below. Remove this colon with a text editor -before importing and publishing this revocation certificate. - -:-----BEGIN PGP PUBLIC KEY BLOCK----- -Comment: This is a revocation certificate - -iQE2BCABCgAgFiEECGlX9y5Odm2fDaF8cXYym/PlRnsFAmjIPnQCHQAACgkQcXYy -m/PlRnsCvgf+MHRXigUXuCL9zmx76ZveNHqZ7epKjjtRNvOwnGxsGTS9D3+kKwYm -44ciAWlPxB1IUK9k3HmdD9o1kvEbbrjJ6vwuZl9lkkXZJHWIeczdoEbdmUSHpHDQ -9eq0pWtdRKRjy2Ol/QGo7CrBO2KdGoN2E/ghd6bz2/8VeUqStP42VdEhB7mWIJ1L -nxD5281lCkTS06r7wviOZgJcOLQ1qPqxHDGAPlR4NPuZNMRG+X9JKoGXJFDCQjgc -Iu+sepH5O3mw3TgMrj+t6IDO3bhXh4TnxOvbwDHFoAZqcug25d34LPaFeEynerrb -OEIuvy1zCZSw0/scAU0izbVi03XjQ/VGJA== -=8csS ------END PGP PUBLIC KEY BLOCK----- diff --git a/demo/assets/keys/gpg/private-keys-v1.d/AB4746844C0C150A7CD566D9784172C3BEC0ED90.key b/demo/assets/keys/gpg/private-keys-v1.d/AB4746844C0C150A7CD566D9784172C3BEC0ED90.key deleted file mode 100644 index 7cd7b95..0000000 --- a/demo/assets/keys/gpg/private-keys-v1.d/AB4746844C0C150A7CD566D9784172C3BEC0ED90.key +++ /dev/null @@ -1,28 +0,0 @@ -Created: 20250915T162732 -Key: (private-key (rsa (n #00921B86FB1BFE6D65ACAA6D7B7A638FF319BAFE8224 - 34C379AB4479AFB9D7ADC1166FDF4347756D1601837A543F3EE131589C2E6E465DA720 - 4F5F08636F88F05FAA63A6A6DB5579231B59C64762B6A6ABAA1E0FDEAB33317F2AA94B - 558C60A8932613F4BAFE877D4CB62A63A26E46C179279F37479F96DD87E8926E4A425A - 322F91707C0F46BC19D677D9FE3726666A960CF2218AB217D732C6CBB03E46BF131876 - B1099DAB7F9AD46378B3A6B60A75EF2D2EEE18E777416A593B28D605344C290E4249E2 - 3D9874646953347D592EA8558D482B3866A78E2CDA8050B5DB47297CF195BE0B5F9322 - DA3EAF2686AC62A267F27EB5CA00C35E51212C4FF95A0EEC55#)(e #010001#)(d - #08F569B53CCF2E812AC397DD855E549A3EA0EE7939C88796B33F8A5291E7069A8901 - 3AC3C099CA7E19EF1603D38D82C10D1E00BED1F3B61D860E14632A2AE21367039FC705 - 5F00BAA0C568D8D3FA3F611EF35A5C5394FF48F3D92146ACDC66AC0043478EADF98516 - 4EA0002FACC5E74EC2075477AE84ACE61EE2215A8526278384DFA7B5A4CF99CD1933DB - AD5151EAD783AD9705E04645E13AC6012087B712A581D0D00398D3FBE6711D0907ABEF - C0AC4F74A07AE301DA85BB471476271CC7B2CF6430AEC447C73EDD8AECFA0278DD31F9 - 607FD99C3FA7227955E289A46960B24DD2D97BED1123D0940965F1CFCB5DF91728845E - 8BDA6009C9D577AF62480215#)(p #00C09B034311E2811DCC15C5749CB52D0D36F1C1 - 266D67744934C6D2A191B6C6C9BE5D8C79C349C8C7BD70F03D2716D626062D99CD4197 - 1F2CE671D92D19C37EB340C7FE2EA05CC3D5103048588AA139CDC43E1F762A3F473AF2 - 91CC5A3556831FEAB31C606B2B07A843379D2B1CC0D5FDAB85C0010AE0BF9A31CDF5BB - BC094967#)(q #00C232952C6CB923DB790118AD52E3C9247E0D7495913A381D77BF77 - E4B6F3611A429828362C7074F71D69C7C102E827914D0615855F13E709A19F665A11AF - DFFD0BBA6135B93161987FBF2BA523434A1C15BADA78602A93627903980028E904322E - D8C6184B68470FBC617C776ADCAB448953377FAF111AF7DE83FAA518D2BAE3#)(u - #04E4F151931458CD7B6A1437C320DBB25DF8E04D119065C1912664322FFF25C5FC3E - 4F4F56F448C4901D9AA24BC276AD7458D5CE35EBBB010613434BEEA950A5D00C8A4288 - 217E53C121C1C52AC3F550E5D79CE4F1E0D9005E4EE4030834FDC3D29A2FA24450FB31 - 5D054A2F50625C78E759A0469BE3D61E9EC297CF3B7A9550#))) diff --git a/demo/assets/keys/gpg/public.key b/demo/assets/keys/gpg/public.key deleted file mode 100644 index b8683f7..0000000 --- a/demo/assets/keys/gpg/public.key +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -mQENBGjIPnQBCACSG4b7G/5tZayqbXt6Y4/zGbr+giQ0w3mrRHmvudetwRZv30NH -dW0WAYN6VD8+4TFYnC5uRl2nIE9fCGNviPBfqmOmpttVeSMbWcZHYramq6oeD96r -MzF/KqlLVYxgqJMmE/S6/od9TLYqY6JuRsF5J583R5+W3Yfokm5KQloyL5FwfA9G -vBnWd9n+NyZmapYM8iGKshfXMsbLsD5GvxMYdrEJnat/mtRjeLOmtgp17y0u7hjn -d0FqWTso1gU0TCkOQkniPZh0ZGlTNH1ZLqhVjUgrOGanjizagFC120cpfPGVvgtf -kyLaPq8mhqxiomfyfrXKAMNeUSEsT/laDuxVABEBAAG0LkRlbW8gU2lnbmluZyBT -ZXJ2aWNlIDxzaWduaW5nQHB1bHAtZGVtby5sb2NhbD6JAVIEEwEKADwWIQQIaVf3 -Lk52bZ8NoXxxdjKb8+VGewUCaMg+dAMbLwQFCwkIBwICIgIGFQoJCAsCBBYCAwEC -HgcCF4AACgkQcXYym/PlRnvN2wf/XIZmHjIRJIM0yfjCXmChRtEySFj6D3OrnNQB -AG3kTNRMIm4HiH/bY9Rc0hGkl7Q636Z0GAc79HX7UMb7kebTRByneyo5/S4cvel6 -S0wulEK5Tq45yDX4hczD+8wrO0u8Ma2CrFiK5LXn0up5A76SYzvaSCHgfjzC9adD -y5HLwtw1TiZMEvFIFZlPaG2ea8wXOsVyTALdIISIMi00ecfILHnR2in2KbmfiNqc -cdxaWCrow8E+MdeWW084CIGOtAzS687IWTDj8iAUMic3vSWBmS6/DAVSKbTyFDfe -OylY5s/Q34G2Fr3neqSB04TwvGIEykuBGy3Kr60aYK/aBrEGIQ== -=PwWq ------END PGP PUBLIC KEY BLOCK----- diff --git a/demo/assets/keys/gpg/pubring.kbx b/demo/assets/keys/gpg/pubring.kbx deleted file mode 100644 index 5b775ba0ea2bef70faef0bd4b93dc0873c1387ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 843 zcmZQzU{GLWWMJ}kib!Jsf{YV(B_Ir9Ljbcj6C;=v$H2g}lYx;zh9filyPjWJ?tI>b zHHBqHvp+v|s|Ko2fT#zm;9+23tON4&z#@zcEKnIZ1+!r$BQFbx&B(zpNxJQ~^uOHH zHLG%~tCIUaOYZvDq+)WoaR*7Cx0A@OIYZ3(a0m>7k}~UIae4Na-aBI z@lncS@2J0>d?n_R;F9TEtnM!>kzlv}Qu;gK*zbwYF1yGquhz2st0%MfWtF#&-V~>u ze(NkxnEq%zbNKfeZENp6hHIPFM07pb`ux(XO6Gl&lC5ufC_bpOIrMe8^XZAF58W~K zQ}Yq}=pj1OKO=Wu_8D=jqeVVUcNJPXjC4&ZkDt(~ym(9Vo952>9k=Eb-ieCPdU5!m zo#FLq(f$@3jeT2qF1q1(^;V IjqJ1l0P0Xbk^lez diff --git a/demo/assets/keys/gpg/pubring.kbx~ b/demo/assets/keys/gpg/pubring.kbx~ deleted file mode 100644 index 20bf8b33832543a3b69d53caa488904aed21be3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32 ecmZQzU{GLWWMJ}kib!Jsf{YV(B_Ir9g8%?fg#|PK diff --git a/demo/assets/keys/gpg/trustdb.gpg b/demo/assets/keys/gpg/trustdb.gpg deleted file mode 100644 index 547769f8fa46b33c6acccebeb8751248e48b5172..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1280 zcmZQfFGy!*W@Ke#VqnO)bz~_6cEHGmT^vQI9Y#v2V6SV*AKqv3)Y z9x&cW3l|;+j?D1ydVXcO^LZE66qXsy{`}Ofnhg>h@~}{0VBlr=rWMZkzftDa&%Bkq S+4^dMEz8%cK-D3XF#rJ5-WSUN From 37a08cea1b8e3e494bea634986cfd4e3fd2bdef2 Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Mon, 6 Oct 2025 14:48:47 -0400 Subject: [PATCH 21/24] Gitignore the keymaterial generated by 'make setup-keys' Signed-off-by: Geoff Wilson --- .gitignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 900e0fb..0790e5c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,9 @@ venv # Demo environment runtime files demo/assets/certs/* !demo/assets/certs/.gitkeep -demo/assets/keys/gpg/* -!demo/assets/keys/.gitkeep +demo/assets/keys/gpg/ +demo/assets/keys/*.pem +demo/assets/keys/*.key # Other runtime files .coverage From 8ec4a42e12a0ca8d48002c0f57f481ff1e9bf9e5 Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Mon, 6 Oct 2025 15:41:45 -0400 Subject: [PATCH 22/24] Simplify make tasks (just need 'demo') and move more stuff to ansible playbook Signed-off-by: Geoff Wilson --- Makefile | 71 ++------- demo/ansible/playbook.yml | 140 +++++++++++++++++- .../assets/scripts/pulp-entrypoint-wrapper.sh | 22 --- demo/assets/scripts/setup-signing-service.sh | 16 -- demo/docker-compose.yml | 4 - 5 files changed, 144 insertions(+), 109 deletions(-) delete mode 100755 demo/assets/scripts/pulp-entrypoint-wrapper.sh delete mode 100755 demo/assets/scripts/setup-signing-service.sh diff --git a/Makefile b/Makefile index 3e37330..5b9d5f4 100644 --- a/Makefile +++ b/Makefile @@ -14,11 +14,7 @@ h help: "c|cover" "Run coverage for all tests" \ "venv" "Create virtualenv" \ "clean" "Clean workspace" \ - "setup-keys" "Generates keys for use in local cluster" \ - "run-pulp3" "Start Pulp 3 locally with Docker Compose" \ - "run-pulp-manager" "Start Pulp Manager with Docker Compose" \ - "run-cluster" "Start Pulp3 + Pulp Manger local cluster with Docker Compose" \ - "setup-demo" "Setup complete demo environment with repositories and packages" + "demo" "Run demo environment" .PHONY : l lint l lint: venv @@ -50,35 +46,17 @@ c cover: venv check-devcontainer coverage run --source=. --omit=pulp_manager/tests/unit/mock_repository.py -m pytest -v && coverage report --fail-under=90; \ coverage html +.PHONY : venv venv: requirements.txt @python3 -m venv venv @. venv/bin/activate; \ pip install --upgrade pip; \ pip install -r requirements.txt -run-pulp-manager: setup-network setup-keys - @echo "Starting Pulp Manager services..." - docker compose -f demo/docker-compose.yml up mariadb redis-manager pulp-manager-api pulp-manager-worker pulp-manager-scheduler --build - -.PHONY : run-pulp3 -run-pulp3: setup-network - @echo "Starting simplified Pulp 3 primary and secondary..." - docker compose -f demo/docker-compose.yml up pulp-primary pulp-secondary --build - -.PHONY : run-cluster -run-cluster: setup-network setup-keys - @echo "Starting complete simplified cluster with Pulp Manager, Primary and Secondary Pulp instances..." - docker compose -f demo/docker-compose.yml up --build - -setup-network: - @echo "Creating or verifying network..." - docker network inspect pulp-net >/dev/null 2>&1 || \ - docker network create pulp-net - @echo "Network setup completed." - +.PHONY : setup-keys setup-keys: @echo "Checking for Pulp encryption keys..." - @mkdir -p demo/assets/certs demo/assets/keys demo/assets/nginx-conf + @mkdir -p demo/assets/certs demo/assets/keys/gpg demo/assets/nginx-conf @if [ ! -f demo/assets/certs/database_fields.symmetric.key ]; then \ echo "Generating database encryption key..."; \ openssl rand -base64 32 > demo/assets/certs/database_fields.symmetric.key; \ @@ -86,15 +64,6 @@ setup-keys: else \ echo "Database encryption key already exists."; \ fi - @if [ ! -f demo/assets/keys/container_auth_private_key.pem ]; then \ - echo "Generating container auth keys..."; \ - openssl ecparam -genkey -name secp256r1 -noout -out demo/assets/keys/container_auth_private_key.pem; \ - openssl ec -in demo/assets/keys/container_auth_private_key.pem -pubout -out demo/assets/keys/container_auth_public_key.pem; \ - echo "Container auth keys created."; \ - else \ - echo "Container auth keys already exist."; \ - fi - @mkdir -p demo/assets/keys/gpg @if [ ! -f demo/assets/keys/gpg/public.key ] || [ ! -s demo/assets/keys/gpg/public.key ]; then \ echo "Generating GPG signing keys..."; \ rm -rf demo/assets/keys/gpg/*; \ @@ -106,38 +75,20 @@ setup-keys: echo "Expire-Date: 0" >> /tmp/gpg-batch-config; \ echo "%no-protection" >> /tmp/gpg-batch-config; \ echo "%commit" >> /tmp/gpg-batch-config; \ + GNUPGHOME=demo/assets/keys/gpg gpg-agent --daemon 2>/dev/null || true; \ GNUPGHOME=demo/assets/keys/gpg gpg --batch --no-default-keyring --keyring demo/assets/keys/gpg/pubring.kbx --gen-key /tmp/gpg-batch-config; \ GNUPGHOME=demo/assets/keys/gpg gpg --no-default-keyring --keyring demo/assets/keys/gpg/pubring.kbx --armor --export > demo/assets/keys/gpg/public.key; \ + GNUPGHOME=demo/assets/keys/gpg gpgconf --kill gpg-agent 2>/dev/null || true; \ rm /tmp/gpg-batch-config; \ echo "GPG signing keys created."; \ else \ echo "GPG signing keys already exist."; \ fi -.PHONY : setup-demo -setup-demo: +.PHONY : demo +demo: venv setup-keys @echo "Setting up demo environment..." - @docker run --rm \ - --network pulp-net \ - -v $(PWD)/demo/ansible:/ansible:ro \ - -v $(PWD)/demo/assets:/assets:ro \ - cytopia/ansible:latest \ - sh -c "pip3 install -q 'pulp-glue>=0.29.0' 'pulp-glue-deb>=0.3.0,<0.4' 2>&1 && \ + @. venv/bin/activate && \ + pip install -q ansible 'pulp-glue>=0.29.0' 'pulp-glue-deb>=0.3.0,<0.4' && \ ansible-galaxy collection install pulp.squeezer 2>&1 | grep -v 'Installing' && \ - ansible-playbook -i localhost, /ansible/playbook.yml" - @echo "" - @echo "Demo Setup Complete" - @echo "===================" - @echo "" - @echo "Available repositories:" - @echo " - ext-small-repo (external): http://localhost:8000/pulp/content/ext-small-repo/" - @echo " - int-demo-packages (internal): http://localhost:8000/pulp/content/int-demo-packages/" - @echo "" - @echo "Pulp Manager sync commands:" - @echo " # Sync internal repositories:" - @echo " curl -X POST 'http://localhost:8080/v1/pulp_servers/2/sync_repos' -H 'Content-Type: application/json' -d '{\"max_runtime\": \"3600\", \"max_concurrent_syncs\": 5, \"regex_include\": \"int-.*\", \"regex_exclude\": \"\"}'" - @echo "" - @echo " # Sync external repositories:" - @echo " curl -X POST 'http://localhost:8080/v1/pulp_servers/2/sync_repos' -H 'Content-Type: application/json' -d '{\"max_runtime\": \"3600\", \"max_concurrent_syncs\": 5, \"regex_include\": \"ext-.*\", \"regex_exclude\": \"\"}'" - @echo "" - @echo "Monitor tasks: http://localhost:9181" + ansible-playbook -i localhost, demo/ansible/playbook.yml diff --git a/demo/ansible/playbook.yml b/demo/ansible/playbook.yml index b82113b..bf14385 100644 --- a/demo/ansible/playbook.yml +++ b/demo/ansible/playbook.yml @@ -1,6 +1,5 @@ --- -# Ansible playbook to set up Pulp demo environment -# Replaces setup-demo.sh with infrastructure-as-code approach +# Ansible playbook to set up Pulp Manager and Pulp Server demo environment - name: Setup Pulp Demo Environment hosts: localhost @@ -8,13 +7,28 @@ gather_facts: false vars: - pulp_primary_url: "http://demo-pulp-primary-1" - pulp_secondary_url: "http://demo-pulp-secondary-1" + pulp_primary_url: "http://localhost:8000" + pulp_secondary_url: "http://localhost:8001" + pulp_manager_url: "http://localhost:8080" pulp_username: "admin" pulp_password: "password" - package_file: "/assets/packages/hello_2.10-2_amd64.deb" + project_dir: "{{ playbook_dir }}/../.." + package_file: "{{ playbook_dir }}/../../demo/assets/packages/hello_2.10-2_amd64.deb" tasks: + - name: Setup Docker network + shell: docker network inspect pulp-net >/dev/null 2>&1 || docker network create pulp-net + + - name: Setup encryption keys + shell: make setup-keys + args: + chdir: "{{ project_dir }}" + + - name: Start Docker Compose cluster + shell: docker compose -f demo/docker-compose.yml up -d --build + args: + chdir: "{{ project_dir }}" + - name: Wait for Pulp Primary to be ready pulp.squeezer.status: pulp_url: "{{ pulp_primary_url }}" @@ -22,7 +36,7 @@ password: "{{ pulp_password }}" register: primary_status until: primary_status is not failed - retries: 30 + retries: 60 delay: 5 - name: Wait for Pulp Secondary to be ready @@ -32,9 +46,39 @@ password: "{{ pulp_password }}" register: secondary_status until: secondary_status is not failed - retries: 30 + retries: 60 delay: 5 + - name: Register signing service on primary + shell: | + docker exec demo-pulp-primary-1 sh -c ' + existing=$(pulpcore-manager shell -c "from pulpcore.app.models import SigningService; print(SigningService.objects.filter(name=\"deb_signing_service\").exists())" 2>/dev/null) + if [ "$existing" != "True" ]; then + key_id=$(GNUPGHOME=/opt/gpg gpg --list-secret-keys --with-colons 2>/dev/null | grep "^sec:" | cut -d: -f5 | head -1) + if [ -n "$key_id" ]; then + pulpcore-manager add-signing-service deb_signing_service /opt/scripts/deb_sign.sh "$key_id" --gnupghome /opt/gpg 2>/dev/null + echo "Signing service created on primary" + fi + else + echo "Signing service already exists on primary" + fi + ' + + - name: Register signing service on secondary + shell: | + docker exec demo-pulp-secondary-1 sh -c ' + existing=$(pulpcore-manager shell -c "from pulpcore.app.models import SigningService; print(SigningService.objects.filter(name=\"deb_signing_service\").exists())" 2>/dev/null) + if [ "$existing" != "True" ]; then + key_id=$(GNUPGHOME=/opt/gpg gpg --list-secret-keys --with-colons 2>/dev/null | grep "^sec:" | cut -d: -f5 | head -1) + if [ -n "$key_id" ]; then + pulpcore-manager add-signing-service deb_signing_service /opt/scripts/deb_sign.sh "$key_id" --gnupghome /opt/gpg 2>/dev/null + echo "Signing service created on secondary" + fi + else + echo "Signing service already exists on secondary" + fi + ' + - name: Create int-demo-packages repository on primary pulp.squeezer.deb_repository: pulp_url: "{{ pulp_primary_url }}" @@ -55,6 +99,24 @@ state: present register: ext_repo + - name: Create int-demo-packages repository on secondary + pulp.squeezer.deb_repository: + pulp_url: "{{ pulp_secondary_url }}" + username: "{{ pulp_username }}" + password: "{{ pulp_password }}" + name: "int-demo-packages" + description: "Internal demo repository\n base_url: int-demo-packages" + state: present + + - name: Create ext-small-repo repository on secondary + pulp.squeezer.deb_repository: + pulp_url: "{{ pulp_secondary_url }}" + username: "{{ pulp_username }}" + password: "{{ pulp_password }}" + name: "ext-small-repo" + description: "External demo repository\n base_url: ext-small-repo" + state: present + - name: Get int-demo-packages repository details pulp.squeezer.deb_repository: pulp_url: "{{ pulp_primary_url }}" @@ -194,3 +256,67 @@ status_code: [201, 202] when: dist_check.json.count == 0 + - name: Trigger repository discovery on primary server + uri: + url: "{{ pulp_manager_url }}/v1/pulp_servers/1/sync_repos" + method: POST + body_format: json + body: + max_runtime: "3600" + max_concurrent_syncs: 5 + status_code: [200, 201] + register: primary_sync + + - name: Wait for primary server repository discovery + uri: + url: "{{ pulp_manager_url }}/v1/tasks/{{ primary_sync.json.id }}" + method: GET + register: primary_sync_task + until: primary_sync_task.json.state in ["completed", "failed"] + retries: 30 + delay: 2 + + - name: Trigger repository discovery on secondary server + uri: + url: "{{ pulp_manager_url }}/v1/pulp_servers/2/sync_repos" + method: POST + body_format: json + body: + max_runtime: "3600" + max_concurrent_syncs: 5 + status_code: [200, 201] + register: secondary_sync + + - name: Wait for secondary server repository discovery + uri: + url: "{{ pulp_manager_url }}/v1/tasks/{{ secondary_sync.json.id }}" + method: GET + register: secondary_sync_task + until: secondary_sync_task.json.state in ["completed", "failed"] + retries: 30 + delay: 2 + + - name: Display completion message + debug: + msg: + - "" + - "============================================" + - "Demo Setup Complete!" + - "============================================" + - "" + - "Available repositories on primary:" + - " - ext-small-repo: {{ pulp_primary_url }}/pulp/content/ext-small-repo/" + - " - int-demo-packages: {{ pulp_primary_url }}/pulp/content/int-demo-packages/" + - "" + - "Available repositories on secondary:" + - " - ext-small-repo: {{ pulp_secondary_url }}/pulp/content/ext-small-repo/" + - " - int-demo-packages: {{ pulp_secondary_url }}/pulp/content/int-demo-packages/" + - "" + - "Pulp Manager API: {{ pulp_manager_url }}" + - "RQ Dashboard: http://localhost:9181" + - "" + - "To sync repositories from primary to secondary:" + - " curl -X POST '{{ pulp_manager_url }}/v1/pulp_servers/2/sync_repos' \\" + - " -H 'Content-Type: application/json' \\" + - " -d '{\"max_runtime\": \"3600\", \"max_concurrent_syncs\": 2, \"regex_include\": \"^int\", \"source_pulp_server_name\": \"pulp-primary:80\"}'" + - "" diff --git a/demo/assets/scripts/pulp-entrypoint-wrapper.sh b/demo/assets/scripts/pulp-entrypoint-wrapper.sh deleted file mode 100755 index 269a891..0000000 --- a/demo/assets/scripts/pulp-entrypoint-wrapper.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# Wrapper script that runs Pulp's normal entrypoint, then sets up signing service - -# Start Pulp in the background using its normal entrypoint -/usr/local/bin/pulp-api & -PULP_PID=$! - -# Wait for Pulp to be ready -echo "Waiting for Pulp to start..." -for i in $(seq 1 60); do - if curl -s http://localhost/pulp/api/v3/status/ > /dev/null 2>&1; then - echo "Pulp is ready" - break - fi - sleep 2 -done - -# Run signing service setup -/opt/scripts/setup-signing-service.sh - -# Wait for Pulp process -wait $PULP_PID diff --git a/demo/assets/scripts/setup-signing-service.sh b/demo/assets/scripts/setup-signing-service.sh deleted file mode 100755 index 033ce57..0000000 --- a/demo/assets/scripts/setup-signing-service.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh -# Registers the signing service if it doesn't already exist - -existing=$(pulpcore-manager shell -c "from pulpcore.app.models import SigningService; print(SigningService.objects.filter(name='deb_signing_service').exists())" 2>/dev/null) - -if [ "$existing" != "True" ]; then - key_id=$(GNUPGHOME=/opt/gpg gpg --list-secret-keys --with-colons 2>/dev/null | grep "^sec:" | cut -d: -f5 | head -1) - if [ -n "$key_id" ]; then - pulpcore-manager add-signing-service deb_signing_service /opt/scripts/deb_sign.sh "$key_id" --gnupghome /opt/gpg 2>/dev/null - echo "Signing service 'deb_signing_service' created" - else - echo "No GPG key found, skipping signing service setup" - fi -else - echo "Signing service 'deb_signing_service' already exists" -fi diff --git a/demo/docker-compose.yml b/demo/docker-compose.yml index 016e716..e89858d 100644 --- a/demo/docker-compose.yml +++ b/demo/docker-compose.yml @@ -15,8 +15,6 @@ services: - "/dev/fuse" networks: - pulp-net - entrypoint: ["/bin/bash", "-c"] - command: ["sleep 10 && /opt/scripts/setup-signing-service.sh & /usr/local/bin/pulp-api"] pulp-secondary: image: pulp/pulp:stable @@ -34,8 +32,6 @@ services: - "/dev/fuse" networks: - pulp-net - entrypoint: ["/bin/bash", "-c"] - command: ["sleep 10 && /opt/scripts/setup-signing-service.sh & /usr/local/bin/pulp-api"] # Pulp Manager services mariadb: From 8e1a0ad2dc1310f9faf1184dc669ecc5f17ffa03 Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Mon, 6 Oct 2025 15:57:01 -0400 Subject: [PATCH 23/24] Move the setup-keys steps to ansible --- Makefile | 34 +-------------------- demo/ansible/playbook.yml | 64 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 37 deletions(-) diff --git a/Makefile b/Makefile index 5b9d5f4..ac99ea0 100644 --- a/Makefile +++ b/Makefile @@ -53,40 +53,8 @@ venv: requirements.txt pip install --upgrade pip; \ pip install -r requirements.txt -.PHONY : setup-keys -setup-keys: - @echo "Checking for Pulp encryption keys..." - @mkdir -p demo/assets/certs demo/assets/keys/gpg demo/assets/nginx-conf - @if [ ! -f demo/assets/certs/database_fields.symmetric.key ]; then \ - echo "Generating database encryption key..."; \ - openssl rand -base64 32 > demo/assets/certs/database_fields.symmetric.key; \ - echo "Database encryption key created."; \ - else \ - echo "Database encryption key already exists."; \ - fi - @if [ ! -f demo/assets/keys/gpg/public.key ] || [ ! -s demo/assets/keys/gpg/public.key ]; then \ - echo "Generating GPG signing keys..."; \ - rm -rf demo/assets/keys/gpg/*; \ - chmod 700 demo/assets/keys/gpg; \ - echo "Key-Type: RSA" > /tmp/gpg-batch-config; \ - echo "Key-Length: 2048" >> /tmp/gpg-batch-config; \ - echo "Name-Real: Demo Signing Service" >> /tmp/gpg-batch-config; \ - echo "Name-Email: signing@pulp-demo.local" >> /tmp/gpg-batch-config; \ - echo "Expire-Date: 0" >> /tmp/gpg-batch-config; \ - echo "%no-protection" >> /tmp/gpg-batch-config; \ - echo "%commit" >> /tmp/gpg-batch-config; \ - GNUPGHOME=demo/assets/keys/gpg gpg-agent --daemon 2>/dev/null || true; \ - GNUPGHOME=demo/assets/keys/gpg gpg --batch --no-default-keyring --keyring demo/assets/keys/gpg/pubring.kbx --gen-key /tmp/gpg-batch-config; \ - GNUPGHOME=demo/assets/keys/gpg gpg --no-default-keyring --keyring demo/assets/keys/gpg/pubring.kbx --armor --export > demo/assets/keys/gpg/public.key; \ - GNUPGHOME=demo/assets/keys/gpg gpgconf --kill gpg-agent 2>/dev/null || true; \ - rm /tmp/gpg-batch-config; \ - echo "GPG signing keys created."; \ - else \ - echo "GPG signing keys already exist."; \ - fi - .PHONY : demo -demo: venv setup-keys +demo: venv @echo "Setting up demo environment..." @. venv/bin/activate && \ pip install -q ansible 'pulp-glue>=0.29.0' 'pulp-glue-deb>=0.3.0,<0.4' && \ diff --git a/demo/ansible/playbook.yml b/demo/ansible/playbook.yml index bf14385..b18e0ee 100644 --- a/demo/ansible/playbook.yml +++ b/demo/ansible/playbook.yml @@ -19,10 +19,66 @@ - name: Setup Docker network shell: docker network inspect pulp-net >/dev/null 2>&1 || docker network create pulp-net - - name: Setup encryption keys - shell: make setup-keys - args: - chdir: "{{ project_dir }}" + - name: Create key directories + file: + path: "{{ item }}" + state: directory + mode: '0700' + loop: + - "{{ project_dir }}/demo/assets/certs" + - "{{ project_dir }}/demo/assets/keys/gpg" + - "{{ project_dir }}/demo/assets/nginx-conf" + + - name: Check if database encryption key exists + stat: + path: "{{ project_dir }}/demo/assets/certs/database_fields.symmetric.key" + register: db_key_stat + + - name: Generate database encryption key + shell: openssl rand -base64 32 > "{{ project_dir }}/demo/assets/certs/database_fields.symmetric.key" + when: not db_key_stat.stat.exists + + - name: Check if GPG public key exists + stat: + path: "{{ project_dir }}/demo/assets/keys/gpg/public.key" + register: gpg_key_stat + + - name: Check if GPG public key is empty + stat: + path: "{{ project_dir }}/demo/assets/keys/gpg/public.key" + register: gpg_key_size + when: gpg_key_stat.stat.exists + + - name: Clean GPG directory if key is missing or empty + file: + path: "{{ project_dir }}/demo/assets/keys/gpg" + state: absent + when: not gpg_key_stat.stat.exists or (gpg_key_stat.stat.exists and gpg_key_size.stat.size == 0) + + - name: Recreate GPG directory + file: + path: "{{ project_dir }}/demo/assets/keys/gpg" + state: directory + mode: '0700' + when: not gpg_key_stat.stat.exists or (gpg_key_stat.stat.exists and gpg_key_size.stat.size == 0) + + - name: Generate GPG signing keys + shell: | + cat > /tmp/gpg-batch-config </dev/null || true + GNUPGHOME={{ project_dir }}/demo/assets/keys/gpg gpg --batch --no-default-keyring --keyring {{ project_dir }}/demo/assets/keys/gpg/pubring.kbx --gen-key /tmp/gpg-batch-config + GNUPGHOME={{ project_dir }}/demo/assets/keys/gpg gpg --no-default-keyring --keyring {{ project_dir }}/demo/assets/keys/gpg/pubring.kbx --armor --export > {{ project_dir }}/demo/assets/keys/gpg/public.key + GNUPGHOME={{ project_dir }}/demo/assets/keys/gpg gpgconf --kill gpg-agent 2>/dev/null || true + rm /tmp/gpg-batch-config + when: not gpg_key_stat.stat.exists or (gpg_key_stat.stat.exists and gpg_key_size.stat.size == 0) - name: Start Docker Compose cluster shell: docker compose -f demo/docker-compose.yml up -d --build From e3bfe7b6866e81b5ada50ffba04c0d2589cc124a Mon Sep 17 00:00:00 2001 From: Geoff Wilson Date: Mon, 6 Oct 2025 15:57:24 -0400 Subject: [PATCH 24/24] README updates --- README.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 697903d..afcce7f 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,15 @@ The Pulp Manager application is used to coordinate common Pulp workflows and provide additional reporting capabilities about a -cluster of Pulp servers. It is designed to work with Pulp3. +cluster of Pulp servers. It is designed to work with Pulp3 servers in +a primary/secondary setup. -## About the Project +## Why Pulp Manager? -We recommend that Pulp be operated in a primary/secondary setup. There -is a single Pulp instance known as the Pulp Primary which syncs repos -from the Internet and can also have custom or internal packages -uploaded to it. Secondaries are then configured to sync these -snapshots and internal repos. +Pulp Manager provides centralized orchestration of a clustger of Pulp3 +instances and is particularly usfeful for organizations with +multi-tiered or multi-zone deployments who need coordinated syncs +between primary and secondary servers. Pulp3 doesn't provide a method to schedule the synchronisation of repos, and in some repository types (deb) may require multiple steps @@ -129,18 +129,22 @@ components: ## Quick Start -1. **For Development, use Dev Container** +1. **For Development (running tests, exploring APIs, etc) ** ```bash # Open in VS Code and select action "Dev Containers: Reopen in Container" # Or use the Dev Container CLI: devcontainer up --workspace-folder . ``` + + From a terminal in the devcontainer, 'make t' will run the tests. + -2. **For Demo cluster, use make targets to setup a Docker Compose environment** +2. **For Demo cluster, use the make target to setup a complete Docker Compose environment** ```bash - make run-cluster - make setup-cluster + make demo ``` + + When startup is finished, `docker ps` will show you the components, and all APIs will be listening. For detailed development setup, see the [Development Info](#development-info) section.