From de2895c6d944e21eb104e0a0c6c9711c5d893cf8 Mon Sep 17 00:00:00 2001 From: Mathieu Garcia Date: Tue, 11 Nov 2025 09:55:18 +0000 Subject: [PATCH 01/15] feat(saas): Add custom strategy the fork a catalog item and add customization --- ansible/playbooks/saas/image-forkable.yml | 110 ++++++++++++++++++ .../saas/roles/caddy/tasks/build.yml | 12 +- .../saas/roles/caddy/templates/Dockerfile.j2 | 4 + ui/controllers/api.js | 3 + ui/index.js.map | 37 +++++- ui/public/forms/catalog.html | 7 +- ui/public/forms/catalogfork.html | 99 ++++++++++++++++ ui/public/forms/catalogs.html | 28 +++-- ui/public/forms/variables.html | 5 +- ui/schemas/catalogs.js | 66 ++++++++++- ui/views/index.html | 1 + 11 files changed, 345 insertions(+), 27 deletions(-) create mode 100644 ansible/playbooks/saas/image-forkable.yml create mode 100644 ui/public/forms/catalogfork.html diff --git a/ansible/playbooks/saas/image-forkable.yml b/ansible/playbooks/saas/image-forkable.yml new file mode 100644 index 00000000..2d3d5b64 --- /dev/null +++ b/ansible/playbooks/saas/image-forkable.yml @@ -0,0 +1,110 @@ +--- +- name: Build a Docker image from a catalog item + hosts: "{{ hosts_limit | default('infrastructure') }}" + become: true + gather_facts: true + vars_prompt: + - name: catalog_id + prompt: "Catalog item ID" + private: false + vars: + build_work_dir: "/tmp/{{ catalog }}" + architecture_map: + amd64: amd64 + x86_64: amd64 + armv7l: arm + aarch64: arm64 + arm64: arm64 + upstream_default_arch: "{{ architecture_map[ansible_facts.architecture] }}" + download_dir: "{{ build_work_dir }}/download" + arch_dir: "{{ build_work_dir }}/{{ upstream_default_arch }}" + image_name2: "{{ docker_private_registry.url }}/{% if docker_private_registry.project is defined %}{{ docker_private_registry.project }}/{% endif %}{{ image_name_manual }}:{{ image_version }}" + ui_url: "{{ lookup('ansible.builtin.env', 'SIMPLE_STACK_UI_URL') }}" + ui_user: "{{ lookup('ansible.builtin.env', 'SIMPLE_STACK_UI_USER') }}" + ui_password: "{{ lookup('ansible.builtin.env', 'SIMPLE_STACK_UI_PASSWORD') }}" + + pre_tasks: + - name: Retrieve catalog item from UI + ansible.builtin.uri: + url: "{{ ui_url }}/api" + user: "{{ ui_user }}" + password: "{{ ui_password }}" + method: POST + body_format: json + body: + schema: "catalogs_read/{{ catalog_id }}" + force_basic_auth: true + status_code: 200 + delegate_to: localhost + register: catalog_response + become: false + + - name: Set catalog facts + ansible.builtin.set_fact: + catalog: "{{ catalog_response.json.origin | default(catalog_response.json.name) }}" + name: "{{ catalog_response.json.name }}" + dockerfile_root: "{{ catalog_response.json.dockerfile_root | default('') }}" + dockerfile_nonroot: "{{ catalog_response.json.dockerfile_nonroot | default('') }}" + image_name_manual: "{{ catalog_response.json.name | lower }}" + + - name: Ensure temporary build directories exist + ansible.builtin.file: + path: "{{ arch_dir }}" + state: directory + mode: "0755" + loop: + - "{{ download_dir }}" + - "{{ arch_dir }}" + + tasks: + - name: Build service-specific assets (e.g., compile, fetch deps) + ansible.builtin.include_role: + name: "{{ catalog }}" + tasks_from: build + + - name: Build and push Docker image (single source of truth) + community.docker.docker_image_build: + name: "{{ image_name2 }}" + tag: latest + path: "{{ build_work_dir }}" + dockerfile: Dockerfile + labels: "{{ image_labels | default({}) }}" + rebuild: always + outputs: + - type: image + push: true + register: docker_build + when: image_build + notify: Cleanup build directory + + - name: Update catalog item version on UI + ansible.builtin.uri: + url: "{{ ui_url }}/api" + user: "{{ ui_user }}" + password: "{{ ui_password }}" + method: POST + body_format: json + body: + schema: catalogs_create + data: + name: "{{ image_name_manual }}" + version: "{{ image_version }}" + force_basic_auth: true + status_code: 200 + delegate_to: localhost + register: ui_update + failed_when: ui_update.status != 200 + become: false + + handlers: + - name: Cleanup build directory + ansible.builtin.file: + path: "{{ build_work_dir }}" + state: absent + listen: cleanup_build + + post_tasks: + - name: Trigger cleanup on failure + ansible.builtin.meta: clear_host_errors + when: ansible_failed_result is defined + notify: Cleanup build directory diff --git a/ansible/playbooks/saas/roles/caddy/tasks/build.yml b/ansible/playbooks/saas/roles/caddy/tasks/build.yml index 10ddbe37..6a5094e3 100644 --- a/ansible/playbooks/saas/roles/caddy/tasks/build.yml +++ b/ansible/playbooks/saas/roles/caddy/tasks/build.yml @@ -9,15 +9,9 @@ image_labels: "{{ image.labels }}" image_build: "{{ image.build }}" -- debug: - msg: - - "{{ softwares }}" - - "{{ softwares[image.name] }}" - - "{{ image_version }}" - -- name: End playbook if no new version - ansible.builtin.meta: end_host - when: softwares[image.name] is defined and softwares[image.name].version == image_version +# - name: End playbook if no new version +# ansible.builtin.meta: end_host +# when: softwares[image.name] is defined and softwares[image.name].version == image_version - name: Download Github release ansible.builtin.get_url: diff --git a/ansible/playbooks/saas/roles/caddy/templates/Dockerfile.j2 b/ansible/playbooks/saas/roles/caddy/templates/Dockerfile.j2 index a423587b..f21f7848 100644 --- a/ansible/playbooks/saas/roles/caddy/templates/Dockerfile.j2 +++ b/ansible/playbooks/saas/roles/caddy/templates/Dockerfile.j2 @@ -4,6 +4,8 @@ FROM {{ image.origin }} ARG TARGETARCH +{{ dockerfile_root | default('') }} + COPY ./${TARGETARCH}/caddy /usr/local/bin/caddy RUN addgroup caddy \ @@ -15,4 +17,6 @@ RUN mkdir -p /var/log/caddy /var/lib/caddy /etc/caddy \ USER caddy +{{ dockerfile_nonroot | default('') }} + CMD ["caddy", "run"] diff --git a/ui/controllers/api.js b/ui/controllers/api.js index d75d2620..73247c4b 100644 --- a/ui/controllers/api.js +++ b/ui/controllers/api.js @@ -24,6 +24,9 @@ exports.install = function() { ROUTE('+API /api/ +catalogs_read/{id} --> Catalogs/read'); ROUTE('+API /api/ +catalogs_create --> Catalogs/create'); ROUTE('+API /api/ +catalogs_update/{id} --> Catalogs/update'); + ROUTE('+API /api/ +catalogs_fork_create --> Catalogs/fork_create'); + ROUTE('+API /api/ +catalogs_fork_update/{id} --> Catalogs/fork_update'); + ROUTE('+API /api/ +catalogs_fork_remove --> Catalogs/fork_remove'); ROUTE('+API /api/ +catalogs_remove/{id} --> Catalogs/remove'); ROUTE('+API /api/ +catalogs_execute/{id} --> Catalogs/execute'); diff --git a/ui/index.js.map b/ui/index.js.map index aa87a415..76360003 100644 --- a/ui/index.js.map +++ b/ui/index.js.map @@ -106,7 +106,7 @@ "url": "/api/", "auth": 1, "id": "catalogs", - "name": " of catalog items" + "name": "List of catalog items" }, { "method": "API", @@ -133,6 +133,30 @@ "input": "*alias:String, *description:String, documentation:String, *cron:Boolean, *crontab:String", "name": "Update catalog item" }, + { + "method": "API", + "url": "/api/", + "auth": 1, + "id": "catalogs_fork_create", + "input": "*origin:String, *version:String, *suffix:String, *alias:String, *description:String, *cron:Boolean, *crontab:String, *dockerfile_root:String, *dockerfile_nonroot:String", + "name": "Fork catalog item" + }, + { + "method": "API", + "url": "/api/", + "auth": 1, + "params": "id:string", + "id": "catalogs_fork_update", + "input": "*origin:String, *suffix:String, *alias:String, *description:String, *cron:Boolean, *crontab:String, *dockerfile_root:String, *dockerfile_nonroot:String", + "name": "Fork catalog item" + }, + { + "method": "API", + "url": "/api/", + "auth": 1, + "id": "catalogs_fork_remove", + "error": "Action not found" + }, { "method": "API", "url": "/api/", @@ -359,6 +383,17 @@ "input": "*name:String, *version:String", "permissions": "catalogs" }, + { + "name": "Catalogs/fork_create", + "input": "*origin:String, *version:String, *suffix:String, *alias:String, *description:String, *cron:Boolean, *crontab:String, *dockerfile_root:String, *dockerfile_nonroot:String", + "permissions": "catalogs" + }, + { + "name": "Catalogs/fork_update", + "params": "*id:UID", + "input": "*origin:String, *suffix:String, *alias:String, *description:String, *cron:Boolean, *crontab:String, *dockerfile_root:String, *dockerfile_nonroot:String", + "permissions": "catalogs" + }, { "name": "Catalogs/read", "params": "*id:UID", diff --git a/ui/public/forms/catalog.html b/ui/public/forms/catalog.html index 4cc89db6..9cb50129 100644 --- a/ui/public/forms/catalog.html +++ b/ui/public/forms/catalog.html @@ -1,4 +1,4 @@ -