diff --git a/README.md b/README.md index 85a65db..6ee717b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,154 @@ -# ansible-role-narratio +# Ansible Role: Narratio -Ansible role to install the Narratio D&D transcript and session report generation suite. \ No newline at end of file +Installs and configures [Narratio](https://gitea.maximumdirect.net/eric/narratio) +from source on Ubuntu 26.04 (`x86_64` or `aarch64`). The role requires gathered +facts and uses task-level privilege escalation for dependencies and managed +application state. + +## Variables + +```yaml +narratio_version: v2.0.0 +narratio_repository_url: https://gitea.maximumdirect.net/eric/narratio.git +narratio_source_dir: /usr/local/src/narratio +narratio_binary_path: /usr/local/bin/narratio +narratio_manage_build_dependencies: true +narratio_go_snap_channel: 1.26/stable +narratio_go_binary: /snap/bin/go +narratio_user: narratio +narratio_group: narratio +narratio_config_path: /usr/local/etc/narratio/pipeline.yml +narratio_workspace_root: /var/lib/narratio +narratio_campaigns_root: /usr/local/share/narratio/campaigns +narratio_spool_root: /var/spool/narratio +narratio_cache_root: /var/cache/narratio +``` + +`narratio_whisperx_transcribe_url` is required and must be an absolute HTTP or +HTTPS URL. Every other pipeline setting is optional and defaults to `null`: + +```yaml +narratio_configkit_imports: null +narratio_workspace_cleanup_after_publish: null +narratio_campaigns_default_campaign_id: null +narratio_storage_backend: null +narratio_storage_s3_bucket: null +narratio_storage_s3_root_prefix: null +narratio_storage_s3_region: null +narratio_storage_s3_endpoint: null +narratio_storage_s3_force_path_style: null +narratio_storage_s3_access_key_id: null +narratio_storage_s3_secret_access_key: null +narratio_spool_delete_audio_after_publish: null +narratio_cache_s3_audio: null +narratio_publish_enabled: null +narratio_publish_upload_run: null +narratio_publish_outputs: null +narratio_whisperx_language: null +narratio_whisperx_timeout: null +narratio_whisperx_retries: null +narratio_whisperx_retry_delay: null +narratio_whisperx_concurrency: null +narratio_seriatim_binary: null +narratio_seriatim_timeout: null +narratio_seriatim_output_schema: null +narratio_seriatim_coalesce_gap: null +narratio_seriatim_report: null +narratio_seriatim_env_overlap_word_run_gap: null +narratio_seriatim_env_overlap_word_run_reorder_window: null +narratio_seriatim_env_backchannel_max_duration: null +narratio_seriatim_env_filler_max_duration: null +narratio_audita_binary: null +narratio_audita_timeout: null +narratio_audita_llm_api_key: null +narratio_audita_modules: null +narratio_audita_base_url: null +narratio_audita_model: null +narratio_audita_total_llm_concurrency: null +narratio_audita_proposal_llm_concurrency: null +narratio_audita_validation_model: null +narratio_audita_validation_llm_concurrency: null +narratio_audita_transcript_description: null +narratio_audita_config_path: null +narratio_audita_output_schema: null +narratio_audita_work_dir_retention: null +narratio_audita_report: null +narratio_normalize_output_path: null +narratio_normalize_output_schema: null +narratio_normalize_report: null +narratio_trim_enabled: null +narratio_trim_output_path: null +narratio_trim_bounds_prompt_id: null +narratio_trim_bounds_profile_id: null +narratio_trim_bounds_transcript_input_name: null +narratio_trim_bounds_output_path: null +narratio_trim_bounds_timeout: null +narratio_trim_bounds_render_debug: null +narratio_trim_bounds_render_output_path: null +narratio_trim_seriatim_report: null +narratio_render_enabled: null +narratio_render_format: null +narratio_render_title: null +narratio_render_include_timestamps: null +narratio_render_include_segment_ids: null +narratio_render_include_metadata: null +narratio_scriptorium_binary: null +narratio_scriptorium_config_path: null +narratio_scriptorium_timeout: null +narratio_scriptorium_render_debug: null +narratio_scriptorium_artifacts: null +narratio_scriptorium_artifact_families: null +narratio_notarius_enabled: null +narratio_notarius_binary: null +narratio_notarius_config_path: null +narratio_notarius_pipeline_id: null +narratio_notarius_timeout: null +narratio_notarius_working_directory: null +narratio_notarius_references: null +narratio_notarius_outputs: null +narratio_notification_mode: null +``` + +With dependency management enabled, the role installs Git and the classic Go +Snap from `narratio_go_snap_channel`. When disabled, Git and the executable at +`narratio_go_binary` must already be available. The role checks out +`narratio_version`, builds `./cmd/narratio` to `narratio_binary_path`, and +rebuilds only when the checkout changes or the binary is absent. + +The `narratio` account owns the workspace, spool, and cache roots, which are +group-writable setgid directories. The campaign registry is `root:narratio` +and operator-readable; its content remains caller-managed. The configuration +directory is `root:narratio` mode `0750`, and the pipeline is mode `0640`. + +Null pipeline values and their empty parent mappings are omitted; non-null +falsey values are preserved. `narratio_publish_outputs`, +`narratio_notarius_references`, `narratio_notarius_outputs`, +`narratio_scriptorium_artifacts`, and `narratio_scriptorium_artifact_families` +are complete native caller values, so callers may compose them before assigning +them and the role does not merge or rewrite them. Credential fields name +environment variables only; credential values remain outside this role. +ConfigKit imports and profile overlays are externally managed, and this role +does not install the downstream applications, campaign content, or suite policy. + +## Example + +```yaml +- hosts: narratio_hosts + roles: + - role: ansible-role-narratio + vars: + narratio_whisperx_transcribe_url: https://whisperx.example.test/transcribe +``` + +## Validation + +The repository-local integration suite installs its pinned collection, runs +syntax checking and production-profile lint, validates a baseline managed +pipeline through Narratio as its application account, and requires immediate +clean repeated convergence. It restores the baseline deployment during cleanup. + +```sh +tests/integration +``` + +Ansible role to install the Narratio D&D transcript and session report generation suite. diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..2d4c5f9 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,2 @@ +[defaults] +roles_path = .. diff --git a/collections/requirements.yml b/collections/requirements.yml new file mode 100644 index 0000000..22c02b2 --- /dev/null +++ b/collections/requirements.yml @@ -0,0 +1,4 @@ +--- +collections: + - name: community.general + version: 13.3.0 diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..a203c4e --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,94 @@ +--- +narratio_version: v2.0.0 +narratio_repository_url: https://gitea.maximumdirect.net/eric/narratio.git +narratio_source_dir: /usr/local/src/narratio +narratio_binary_path: /usr/local/bin/narratio +narratio_manage_build_dependencies: true +narratio_go_snap_channel: 1.26/stable +narratio_go_binary: /snap/bin/go +narratio_user: narratio +narratio_group: narratio +narratio_config_path: /usr/local/etc/narratio/pipeline.yml +narratio_workspace_root: /var/lib/narratio +narratio_campaigns_root: /usr/local/share/narratio/campaigns +narratio_spool_root: /var/spool/narratio +narratio_cache_root: /var/cache/narratio +narratio_configkit_imports: null +narratio_workspace_cleanup_after_publish: null +narratio_campaigns_default_campaign_id: null +narratio_storage_backend: null +narratio_storage_s3_bucket: null +narratio_storage_s3_root_prefix: null +narratio_storage_s3_region: null +narratio_storage_s3_endpoint: null +narratio_storage_s3_force_path_style: null +narratio_storage_s3_access_key_id: null +narratio_storage_s3_secret_access_key: null +narratio_spool_delete_audio_after_publish: null +narratio_cache_s3_audio: null +narratio_publish_enabled: null +narratio_publish_upload_run: null +narratio_publish_outputs: null +narratio_whisperx_language: null +narratio_whisperx_timeout: null +narratio_whisperx_retries: null +narratio_whisperx_retry_delay: null +narratio_whisperx_concurrency: null +narratio_seriatim_binary: null +narratio_seriatim_timeout: null +narratio_seriatim_output_schema: null +narratio_seriatim_coalesce_gap: null +narratio_seriatim_report: null +narratio_seriatim_env_overlap_word_run_gap: null +narratio_seriatim_env_overlap_word_run_reorder_window: null +narratio_seriatim_env_backchannel_max_duration: null +narratio_seriatim_env_filler_max_duration: null +narratio_audita_binary: null +narratio_audita_timeout: null +narratio_audita_llm_api_key: null +narratio_audita_modules: null +narratio_audita_base_url: null +narratio_audita_model: null +narratio_audita_total_llm_concurrency: null +narratio_audita_proposal_llm_concurrency: null +narratio_audita_validation_model: null +narratio_audita_validation_llm_concurrency: null +narratio_audita_transcript_description: null +narratio_audita_config_path: null +narratio_audita_output_schema: null +narratio_audita_work_dir_retention: null +narratio_audita_report: null +narratio_normalize_output_path: null +narratio_normalize_output_schema: null +narratio_normalize_report: null +narratio_trim_enabled: null +narratio_trim_output_path: null +narratio_trim_bounds_prompt_id: null +narratio_trim_bounds_profile_id: null +narratio_trim_bounds_transcript_input_name: null +narratio_trim_bounds_output_path: null +narratio_trim_bounds_timeout: null +narratio_trim_bounds_render_debug: null +narratio_trim_bounds_render_output_path: null +narratio_trim_seriatim_report: null +narratio_render_enabled: null +narratio_render_format: null +narratio_render_title: null +narratio_render_include_timestamps: null +narratio_render_include_segment_ids: null +narratio_render_include_metadata: null +narratio_scriptorium_binary: null +narratio_scriptorium_config_path: null +narratio_scriptorium_timeout: null +narratio_scriptorium_render_debug: null +narratio_scriptorium_artifacts: null +narratio_scriptorium_artifact_families: null +narratio_notarius_enabled: null +narratio_notarius_binary: null +narratio_notarius_config_path: null +narratio_notarius_pipeline_id: null +narratio_notarius_timeout: null +narratio_notarius_working_directory: null +narratio_notarius_references: null +narratio_notarius_outputs: null +narratio_notification_mode: null diff --git a/meta/argument_specs.yml b/meta/argument_specs.yml new file mode 100644 index 0000000..f28f2da --- /dev/null +++ b/meta/argument_specs.yml @@ -0,0 +1,99 @@ +--- +argument_specs: + main: + short_description: Install and configure Narratio from source + options: + narratio_version: {type: str, required: true} + narratio_repository_url: {type: str, required: true} + narratio_source_dir: {type: path, required: true} + narratio_binary_path: {type: path, required: true} + narratio_manage_build_dependencies: {type: bool, required: true} + narratio_go_snap_channel: {type: str, required: true} + narratio_go_binary: {type: path, required: true} + narratio_user: {type: str, required: true} + narratio_group: {type: str, required: true} + narratio_config_path: {type: path, required: true} + narratio_workspace_root: {type: path, required: true} + narratio_campaigns_root: {type: path, required: true} + narratio_spool_root: {type: path, required: true} + narratio_cache_root: {type: path, required: true} + narratio_whisperx_transcribe_url: {type: str, required: true} + narratio_configkit_imports: {type: list, required: false} + narratio_workspace_cleanup_after_publish: {type: bool, required: false} + narratio_campaigns_default_campaign_id: {type: str, required: false} + narratio_storage_backend: {type: str, required: false} + narratio_storage_s3_bucket: {type: str, required: false} + narratio_storage_s3_root_prefix: {type: str, required: false} + narratio_storage_s3_region: {type: str, required: false} + narratio_storage_s3_endpoint: {type: str, required: false} + narratio_storage_s3_force_path_style: {type: bool, required: false} + narratio_storage_s3_access_key_id: {type: str, required: false} + narratio_storage_s3_secret_access_key: {type: str, required: false} + narratio_spool_delete_audio_after_publish: {type: bool, required: false} + narratio_cache_s3_audio: {type: bool, required: false} + narratio_publish_enabled: {type: bool, required: false} + narratio_publish_upload_run: {type: bool, required: false} + narratio_publish_outputs: {type: list, required: false} + narratio_whisperx_language: {type: str, required: false} + narratio_whisperx_timeout: {type: str, required: false} + narratio_whisperx_retries: {type: int, required: false} + narratio_whisperx_retry_delay: {type: str, required: false} + narratio_whisperx_concurrency: {type: int, required: false} + narratio_seriatim_binary: {type: str, required: false} + narratio_seriatim_timeout: {type: str, required: false} + narratio_seriatim_output_schema: {type: str, required: false} + narratio_seriatim_coalesce_gap: {type: float, required: false} + narratio_seriatim_report: {type: bool, required: false} + narratio_seriatim_env_overlap_word_run_gap: {type: float, required: false} + narratio_seriatim_env_overlap_word_run_reorder_window: {type: float, required: false} + narratio_seriatim_env_backchannel_max_duration: {type: float, required: false} + narratio_seriatim_env_filler_max_duration: {type: float, required: false} + narratio_audita_binary: {type: str, required: false} + narratio_audita_timeout: {type: str, required: false} + narratio_audita_llm_api_key: {type: str, required: false} + narratio_audita_modules: {type: list, required: false} + narratio_audita_base_url: {type: str, required: false} + narratio_audita_model: {type: str, required: false} + narratio_audita_total_llm_concurrency: {type: int, required: false} + narratio_audita_proposal_llm_concurrency: {type: int, required: false} + narratio_audita_validation_model: {type: str, required: false} + narratio_audita_validation_llm_concurrency: {type: int, required: false} + narratio_audita_transcript_description: {type: str, required: false} + narratio_audita_config_path: {type: str, required: false} + narratio_audita_output_schema: {type: str, required: false} + narratio_audita_work_dir_retention: {type: str, required: false} + narratio_audita_report: {type: bool, required: false} + narratio_normalize_output_path: {type: str, required: false} + narratio_normalize_output_schema: {type: str, required: false} + narratio_normalize_report: {type: bool, required: false} + narratio_trim_enabled: {type: bool, required: false} + narratio_trim_output_path: {type: str, required: false} + narratio_trim_bounds_prompt_id: {type: str, required: false} + narratio_trim_bounds_profile_id: {type: str, required: false} + narratio_trim_bounds_transcript_input_name: {type: str, required: false} + narratio_trim_bounds_output_path: {type: str, required: false} + narratio_trim_bounds_timeout: {type: str, required: false} + narratio_trim_bounds_render_debug: {type: bool, required: false} + narratio_trim_bounds_render_output_path: {type: str, required: false} + narratio_trim_seriatim_report: {type: bool, required: false} + narratio_render_enabled: {type: bool, required: false} + narratio_render_format: {type: str, required: false} + narratio_render_title: {type: str, required: false} + narratio_render_include_timestamps: {type: bool, required: false} + narratio_render_include_segment_ids: {type: bool, required: false} + narratio_render_include_metadata: {type: bool, required: false} + narratio_scriptorium_binary: {type: str, required: false} + narratio_scriptorium_config_path: {type: str, required: false} + narratio_scriptorium_timeout: {type: str, required: false} + narratio_scriptorium_render_debug: {type: bool, required: false} + narratio_scriptorium_artifacts: {type: dict, required: false} + narratio_scriptorium_artifact_families: {type: dict, required: false} + narratio_notarius_enabled: {type: bool, required: false} + narratio_notarius_binary: {type: str, required: false} + narratio_notarius_config_path: {type: str, required: false} + narratio_notarius_pipeline_id: {type: str, required: false} + narratio_notarius_timeout: {type: str, required: false} + narratio_notarius_working_directory: {type: str, required: false} + narratio_notarius_references: {type: dict, required: false} + narratio_notarius_outputs: {type: dict, required: false} + narratio_notification_mode: {type: str, required: false} diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..7c4b0ed --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,18 @@ +--- +galaxy_info: + role_name: narratio + namespace: rakestraw + author: Eric Rakestraw + description: Install and configure Narratio from source + license: CC0-1.0 + min_ansible_version: "2.18" + issue_tracker_url: https://gitea.maximumdirect.net/eric/ansible-role-narratio/issues + platforms: + - name: Ubuntu + versions: + - resolute + galaxy_tags: + - narratio + - transcript + - processing +dependencies: [] diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..f082d45 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,147 @@ +--- +- name: Validate the supported platform + ansible.builtin.assert: + that: + - ansible_facts.distribution == "Ubuntu" + - ansible_facts.distribution_version == "26.04" + - ansible_facts.architecture in ["x86_64", "aarch64"] + fail_msg: Narratio supports Ubuntu 26.04 on x86_64 or aarch64. + +- name: Install Git + become: true + ansible.builtin.apt: + name: git + state: present + update_cache: true + cache_valid_time: 3600 + when: narratio_manage_build_dependencies + +- name: Install Go + become: true + community.general.snap: + name: go + channel: "{{ narratio_go_snap_channel }}" + classic: true + state: present + when: narratio_manage_build_dependencies + +- name: Create the Narratio group + become: true + ansible.builtin.group: + name: "{{ narratio_group }}" + system: true + +- name: Create the Narratio account + become: true + ansible.builtin.user: + name: "{{ narratio_user }}" + group: "{{ narratio_group }}" + system: true + home: "{{ narratio_workspace_root }}" + create_home: false + shell: /usr/sbin/nologin + +- name: Create installation parent directories + become: true + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: "0755" + loop: + - "{{ narratio_source_dir | dirname }}" + - "{{ narratio_binary_path | dirname }}" + +- name: Create configuration directory + become: true + ansible.builtin.file: + path: "{{ narratio_config_path | dirname }}" + state: directory + owner: root + group: "{{ narratio_group }}" + mode: "0750" + +- name: Create mutable Narratio roots + become: true + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ narratio_user }}" + group: "{{ narratio_group }}" + mode: "2775" + loop: + - "{{ narratio_workspace_root }}" + - "{{ narratio_spool_root }}" + - "{{ narratio_cache_root }}" + +- name: Create the campaign registry root + become: true + ansible.builtin.file: + path: "{{ narratio_campaigns_root }}" + state: directory + owner: root + group: "{{ narratio_group }}" + mode: "0755" + +- name: Check out Narratio source + become: true + ansible.builtin.git: + repo: "{{ narratio_repository_url }}" + dest: "{{ narratio_source_dir }}" + version: "{{ narratio_version }}" + force: true + register: narratio_checkout + +- name: Inspect the installed binary + ansible.builtin.stat: + path: "{{ narratio_binary_path }}" + register: narratio_binary + +- name: Build Narratio + become: true + ansible.builtin.command: + argv: + - "{{ narratio_go_binary }}" + - build + - -trimpath + - -ldflags + - "-s -w -X gitea.maximumdirect.net/eric/narratio/internal/buildinfo.Version={{ narratio_version }}" + - -o + - "{{ narratio_binary_path }}" + - ./cmd/narratio + chdir: "{{ narratio_source_dir }}" + environment: + CGO_ENABLED: "0" + GOWORK: "off" + GOTOOLCHAIN: local + changed_when: true + when: >- + narratio_checkout.changed or + not narratio_binary.stat.exists + +- name: Converge installed metadata + become: true + ansible.builtin.file: + path: "{{ item.path }}" + state: "{{ item.state }}" + owner: root + group: root + mode: "{{ item.mode | default(omit) }}" + recurse: "{{ item.recurse | default(omit) }}" + loop: + - path: "{{ narratio_source_dir }}" + state: directory + recurse: true + - path: "{{ narratio_binary_path }}" + state: file + mode: "0755" + +- name: Write Narratio pipeline configuration + become: true + ansible.builtin.template: + src: pipeline.yml.j2 + dest: "{{ narratio_config_path }}" + owner: root + group: "{{ narratio_group }}" + mode: "0640" diff --git a/templates/pipeline.yml.j2 b/templates/pipeline.yml.j2 new file mode 100644 index 0000000..972bd9d --- /dev/null +++ b/templates/pipeline.yml.j2 @@ -0,0 +1,286 @@ +{% if narratio_configkit_imports is not none %} +configkit: + imports: {{ narratio_configkit_imports | to_json }} +{% endif %} +workspace: + root: {{ narratio_workspace_root | to_json }} +{% if narratio_workspace_cleanup_after_publish is not none %} + cleanup_after_publish: {{ narratio_workspace_cleanup_after_publish | to_json }} +{% endif %} +campaigns: + root: {{ narratio_campaigns_root | to_json }} +{% if narratio_campaigns_default_campaign_id is not none %} + default_campaign_id: {{ narratio_campaigns_default_campaign_id | to_json }} +{% endif %} +{% if narratio_storage_backend is not none or narratio_storage_s3_bucket is not none or narratio_storage_s3_root_prefix is not none or narratio_storage_s3_region is not none or narratio_storage_s3_endpoint is not none or narratio_storage_s3_force_path_style is not none or narratio_storage_s3_access_key_id is not none or narratio_storage_s3_secret_access_key is not none %} +storage: +{% if narratio_storage_backend is not none %} + backend: {{ narratio_storage_backend | to_json }} +{% endif %} +{% if narratio_storage_s3_bucket is not none or narratio_storage_s3_root_prefix is not none or narratio_storage_s3_region is not none or narratio_storage_s3_endpoint is not none or narratio_storage_s3_force_path_style is not none or narratio_storage_s3_access_key_id is not none or narratio_storage_s3_secret_access_key is not none %} + s3: +{% if narratio_storage_s3_bucket is not none %} + bucket: {{ narratio_storage_s3_bucket | to_json }} +{% endif %} +{% if narratio_storage_s3_root_prefix is not none %} + root_prefix: {{ narratio_storage_s3_root_prefix | to_json }} +{% endif %} +{% if narratio_storage_s3_region is not none %} + region: {{ narratio_storage_s3_region | to_json }} +{% endif %} +{% if narratio_storage_s3_endpoint is not none %} + endpoint: {{ narratio_storage_s3_endpoint | to_json }} +{% endif %} +{% if narratio_storage_s3_force_path_style is not none %} + force_path_style: {{ narratio_storage_s3_force_path_style | to_json }} +{% endif %} +{% if narratio_storage_s3_access_key_id is not none %} + access_key_id: {{ narratio_storage_s3_access_key_id | to_json }} +{% endif %} +{% if narratio_storage_s3_secret_access_key is not none %} + secret_access_key: {{ narratio_storage_s3_secret_access_key | to_json }} +{% endif %} +{% endif %} +{% endif %} +spool: + root: {{ narratio_spool_root | to_json }} +{% if narratio_spool_delete_audio_after_publish is not none %} + delete_audio_after_publish: {{ narratio_spool_delete_audio_after_publish | to_json }} +{% endif %} +cache: + root: {{ narratio_cache_root | to_json }} +{% if narratio_cache_s3_audio is not none %} + s3_audio: {{ narratio_cache_s3_audio | to_json }} +{% endif %} +{% if narratio_publish_enabled is not none or narratio_publish_upload_run is not none or narratio_publish_outputs is not none %} +publish: +{% if narratio_publish_enabled is not none %} + enabled: {{ narratio_publish_enabled | to_json }} +{% endif %} +{% if narratio_publish_upload_run is not none %} + upload_run: {{ narratio_publish_upload_run | to_json }} +{% endif %} +{% if narratio_publish_outputs is not none %} + outputs: {{ narratio_publish_outputs | to_json }} +{% endif %} +{% endif %} +whisperx: + transcribe_url: {{ narratio_whisperx_transcribe_url | to_json }} +{% if narratio_whisperx_language is not none %} + language: {{ narratio_whisperx_language | to_json }} +{% endif %} +{% if narratio_whisperx_timeout is not none %} + timeout: {{ narratio_whisperx_timeout | to_json }} +{% endif %} +{% if narratio_whisperx_retries is not none %} + retries: {{ narratio_whisperx_retries | to_json }} +{% endif %} +{% if narratio_whisperx_retry_delay is not none %} + retry_delay: {{ narratio_whisperx_retry_delay | to_json }} +{% endif %} +{% if narratio_whisperx_concurrency is not none %} + concurrency: {{ narratio_whisperx_concurrency | to_json }} +{% endif %} +{% if narratio_seriatim_binary is not none or narratio_seriatim_timeout is not none or narratio_seriatim_output_schema is not none or narratio_seriatim_coalesce_gap is not none or narratio_seriatim_report is not none or narratio_seriatim_env_overlap_word_run_gap is not none or narratio_seriatim_env_overlap_word_run_reorder_window is not none or narratio_seriatim_env_backchannel_max_duration is not none or narratio_seriatim_env_filler_max_duration is not none %} +seriatim: +{% if narratio_seriatim_binary is not none %} + binary: {{ narratio_seriatim_binary | to_json }} +{% endif %} +{% if narratio_seriatim_timeout is not none %} + timeout: {{ narratio_seriatim_timeout | to_json }} +{% endif %} +{% if narratio_seriatim_output_schema is not none %} + output_schema: {{ narratio_seriatim_output_schema | to_json }} +{% endif %} +{% if narratio_seriatim_coalesce_gap is not none %} + coalesce_gap: {{ narratio_seriatim_coalesce_gap | to_json }} +{% endif %} +{% if narratio_seriatim_report is not none %} + report: {{ narratio_seriatim_report | to_json }} +{% endif %} +{% if narratio_seriatim_env_overlap_word_run_gap is not none or narratio_seriatim_env_overlap_word_run_reorder_window is not none or narratio_seriatim_env_backchannel_max_duration is not none or narratio_seriatim_env_filler_max_duration is not none %} + env: +{% if narratio_seriatim_env_overlap_word_run_gap is not none %} + overlap_word_run_gap: {{ narratio_seriatim_env_overlap_word_run_gap | to_json }} +{% endif %} +{% if narratio_seriatim_env_overlap_word_run_reorder_window is not none %} + overlap_word_run_reorder_window: {{ narratio_seriatim_env_overlap_word_run_reorder_window | to_json }} +{% endif %} +{% if narratio_seriatim_env_backchannel_max_duration is not none %} + backchannel_max_duration: {{ narratio_seriatim_env_backchannel_max_duration | to_json }} +{% endif %} +{% if narratio_seriatim_env_filler_max_duration is not none %} + filler_max_duration: {{ narratio_seriatim_env_filler_max_duration | to_json }} +{% endif %} +{% endif %} +{% endif %} +{% if narratio_audita_binary is not none or narratio_audita_timeout is not none or narratio_audita_llm_api_key is not none or narratio_audita_modules is not none or narratio_audita_base_url is not none or narratio_audita_model is not none or narratio_audita_total_llm_concurrency is not none or narratio_audita_proposal_llm_concurrency is not none or narratio_audita_validation_model is not none or narratio_audita_validation_llm_concurrency is not none or narratio_audita_transcript_description is not none or narratio_audita_config_path is not none or narratio_audita_output_schema is not none or narratio_audita_work_dir_retention is not none or narratio_audita_report is not none %} +audita: +{% if narratio_audita_binary is not none %} + binary: {{ narratio_audita_binary | to_json }} +{% endif %} +{% if narratio_audita_timeout is not none %} + timeout: {{ narratio_audita_timeout | to_json }} +{% endif %} +{% if narratio_audita_llm_api_key is not none %} + llm_api_key: {{ narratio_audita_llm_api_key | to_json }} +{% endif %} +{% if narratio_audita_modules is not none %} + modules: {{ narratio_audita_modules | to_json }} +{% endif %} +{% if narratio_audita_base_url is not none %} + base_url: {{ narratio_audita_base_url | to_json }} +{% endif %} +{% if narratio_audita_model is not none %} + model: {{ narratio_audita_model | to_json }} +{% endif %} +{% if narratio_audita_total_llm_concurrency is not none %} + total_llm_concurrency: {{ narratio_audita_total_llm_concurrency | to_json }} +{% endif %} +{% if narratio_audita_proposal_llm_concurrency is not none %} + proposal_llm_concurrency: {{ narratio_audita_proposal_llm_concurrency | to_json }} +{% endif %} +{% if narratio_audita_validation_model is not none %} + validation_model: {{ narratio_audita_validation_model | to_json }} +{% endif %} +{% if narratio_audita_validation_llm_concurrency is not none %} + validation_llm_concurrency: {{ narratio_audita_validation_llm_concurrency | to_json }} +{% endif %} +{% if narratio_audita_transcript_description is not none %} + transcript_description: {{ narratio_audita_transcript_description | to_json }} +{% endif %} +{% if narratio_audita_config_path is not none %} + config_path: {{ narratio_audita_config_path | to_json }} +{% endif %} +{% if narratio_audita_output_schema is not none %} + output_schema: {{ narratio_audita_output_schema | to_json }} +{% endif %} +{% if narratio_audita_work_dir_retention is not none %} + work_dir_retention: {{ narratio_audita_work_dir_retention | to_json }} +{% endif %} +{% if narratio_audita_report is not none %} + report: {{ narratio_audita_report | to_json }} +{% endif %} +{% endif %} +{% if narratio_normalize_output_path is not none or narratio_normalize_output_schema is not none or narratio_normalize_report is not none %} +normalize: +{% if narratio_normalize_output_path is not none %} + output_path: {{ narratio_normalize_output_path | to_json }} +{% endif %} +{% if narratio_normalize_output_schema is not none %} + output_schema: {{ narratio_normalize_output_schema | to_json }} +{% endif %} +{% if narratio_normalize_report is not none %} + report: {{ narratio_normalize_report | to_json }} +{% endif %} +{% endif %} +{% if narratio_trim_enabled is not none or narratio_trim_output_path is not none or narratio_trim_bounds_prompt_id is not none or narratio_trim_bounds_profile_id is not none or narratio_trim_bounds_transcript_input_name is not none or narratio_trim_bounds_output_path is not none or narratio_trim_bounds_timeout is not none or narratio_trim_bounds_render_debug is not none or narratio_trim_bounds_render_output_path is not none or narratio_trim_seriatim_report is not none %} +trim: +{% if narratio_trim_enabled is not none %} + enabled: {{ narratio_trim_enabled | to_json }} +{% endif %} +{% if narratio_trim_output_path is not none %} + output_path: {{ narratio_trim_output_path | to_json }} +{% endif %} +{% if narratio_trim_bounds_prompt_id is not none or narratio_trim_bounds_profile_id is not none or narratio_trim_bounds_transcript_input_name is not none or narratio_trim_bounds_output_path is not none or narratio_trim_bounds_timeout is not none or narratio_trim_bounds_render_debug is not none or narratio_trim_bounds_render_output_path is not none %} + bounds: +{% if narratio_trim_bounds_prompt_id is not none %} + prompt_id: {{ narratio_trim_bounds_prompt_id | to_json }} +{% endif %} +{% if narratio_trim_bounds_profile_id is not none %} + profile_id: {{ narratio_trim_bounds_profile_id | to_json }} +{% endif %} +{% if narratio_trim_bounds_transcript_input_name is not none %} + transcript_input_name: {{ narratio_trim_bounds_transcript_input_name | to_json }} +{% endif %} +{% if narratio_trim_bounds_output_path is not none %} + output_path: {{ narratio_trim_bounds_output_path | to_json }} +{% endif %} +{% if narratio_trim_bounds_timeout is not none %} + timeout: {{ narratio_trim_bounds_timeout | to_json }} +{% endif %} +{% if narratio_trim_bounds_render_debug is not none %} + render_debug: {{ narratio_trim_bounds_render_debug | to_json }} +{% endif %} +{% if narratio_trim_bounds_render_output_path is not none %} + render_output_path: {{ narratio_trim_bounds_render_output_path | to_json }} +{% endif %} +{% endif %} +{% if narratio_trim_seriatim_report is not none %} + seriatim: + report: {{ narratio_trim_seriatim_report | to_json }} +{% endif %} +{% endif %} +{% if narratio_render_enabled is not none or narratio_render_format is not none or narratio_render_title is not none or narratio_render_include_timestamps is not none or narratio_render_include_segment_ids is not none or narratio_render_include_metadata is not none %} +render: +{% if narratio_render_enabled is not none %} + enabled: {{ narratio_render_enabled | to_json }} +{% endif %} +{% if narratio_render_format is not none %} + format: {{ narratio_render_format | to_json }} +{% endif %} +{% if narratio_render_title is not none %} + title: {{ narratio_render_title | to_json }} +{% endif %} +{% if narratio_render_include_timestamps is not none %} + include_timestamps: {{ narratio_render_include_timestamps | to_json }} +{% endif %} +{% if narratio_render_include_segment_ids is not none %} + include_segment_ids: {{ narratio_render_include_segment_ids | to_json }} +{% endif %} +{% if narratio_render_include_metadata is not none %} + include_metadata: {{ narratio_render_include_metadata | to_json }} +{% endif %} +{% endif %} +{% if narratio_scriptorium_binary is not none or narratio_scriptorium_config_path is not none or narratio_scriptorium_timeout is not none or narratio_scriptorium_render_debug is not none or narratio_scriptorium_artifacts is not none or narratio_scriptorium_artifact_families is not none %} +scriptorium: +{% if narratio_scriptorium_binary is not none %} + binary: {{ narratio_scriptorium_binary | to_json }} +{% endif %} +{% if narratio_scriptorium_config_path is not none %} + config_path: {{ narratio_scriptorium_config_path | to_json }} +{% endif %} +{% if narratio_scriptorium_timeout is not none %} + timeout: {{ narratio_scriptorium_timeout | to_json }} +{% endif %} +{% if narratio_scriptorium_render_debug is not none %} + render_debug: {{ narratio_scriptorium_render_debug | to_json }} +{% endif %} +{% if narratio_scriptorium_artifacts is not none %} + artifacts: {{ narratio_scriptorium_artifacts | to_json }} +{% endif %} +{% if narratio_scriptorium_artifact_families is not none %} + artifact_families: {{ narratio_scriptorium_artifact_families | to_json }} +{% endif %} +{% endif %} +{% if narratio_notarius_enabled is not none or narratio_notarius_binary is not none or narratio_notarius_config_path is not none or narratio_notarius_pipeline_id is not none or narratio_notarius_timeout is not none or narratio_notarius_working_directory is not none or narratio_notarius_references is not none or narratio_notarius_outputs is not none %} +notarius: +{% if narratio_notarius_enabled is not none %} + enabled: {{ narratio_notarius_enabled | to_json }} +{% endif %} +{% if narratio_notarius_binary is not none %} + binary: {{ narratio_notarius_binary | to_json }} +{% endif %} +{% if narratio_notarius_config_path is not none %} + config_path: {{ narratio_notarius_config_path | to_json }} +{% endif %} +{% if narratio_notarius_pipeline_id is not none %} + pipeline_id: {{ narratio_notarius_pipeline_id | to_json }} +{% endif %} +{% if narratio_notarius_timeout is not none %} + timeout: {{ narratio_notarius_timeout | to_json }} +{% endif %} +{% if narratio_notarius_working_directory is not none %} + working_directory: {{ narratio_notarius_working_directory | to_json }} +{% endif %} +{% if narratio_notarius_references is not none %} + references: {{ narratio_notarius_references | to_json }} +{% endif %} +{% if narratio_notarius_outputs is not none %} + outputs: {{ narratio_notarius_outputs | to_json }} +{% endif %} +{% endif %} +{% if narratio_notification_mode is not none %} +notification: + mode: {{ narratio_notification_mode | to_json }} +{% endif %} diff --git a/tests/converge.yml b/tests/converge.yml new file mode 100644 index 0000000..eea942a --- /dev/null +++ b/tests/converge.yml @@ -0,0 +1,6 @@ +--- +- name: Converge Narratio role + hosts: localhost + gather_facts: true + roles: + - role: ansible-role-narratio diff --git a/tests/integration b/tests/integration new file mode 100755 index 0000000..11f9da9 --- /dev/null +++ b/tests/integration @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +repository_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +workspace_venv="$repository_root/../.venv" +test_tmp=$(mktemp -d) +chmod 0700 "$test_tmp" + +export ANSIBLE_COLLECTIONS_PATH="$test_tmp/collections" +export ANSIBLE_ROLES_PATH="$(dirname "$repository_root")" + +inventory="$repository_root/tests/inventory" +converge="$repository_root/tests/converge.yml" +verify_baseline="$repository_root/tests/verify-baseline.yml" +ansible_playbook="$workspace_venv/bin/ansible-playbook" +baseline_args=(-e 'narratio_whisperx_transcribe_url=http://127.0.0.1:8000/transcribe') + +cleanup() { + exit_status=$? + trap - EXIT + "$ansible_playbook" -i "$inventory" "${baseline_args[@]}" "$converge" >/dev/null 2>&1 || true + rm -rf -- "$test_tmp" + exit "$exit_status" +} +trap cleanup EXIT + +require_clean_recap() { + grep -Eq 'changed=0 .*failed=0' "$1" +} + +run_converge() { + output_file=$1 + shift + "$ansible_playbook" -i "$inventory" "$@" "$converge" | tee "$output_file" +} + +cd "$repository_root" +"$workspace_venv/bin/ansible-galaxy" collection install \ + -r collections/requirements.yml \ + -p "$ANSIBLE_COLLECTIONS_PATH" +"$ansible_playbook" -i "$inventory" --syntax-check "$converge" +"$workspace_venv/bin/ansible-lint" "$converge" "$verify_baseline" + +baseline_log="$test_tmp/baseline.log" +run_converge "$baseline_log" "${baseline_args[@]}" +"$ansible_playbook" -i "$inventory" "$verify_baseline" +baseline_repeat_log="$test_tmp/baseline-repeat.log" +run_converge "$baseline_repeat_log" "${baseline_args[@]}" +require_clean_recap "$baseline_repeat_log" diff --git a/tests/inventory b/tests/inventory new file mode 100644 index 0000000..2302eda --- /dev/null +++ b/tests/inventory @@ -0,0 +1 @@ +localhost ansible_connection=local diff --git a/tests/verify-baseline.yml b/tests/verify-baseline.yml new file mode 100644 index 0000000..cd7ae92 --- /dev/null +++ b/tests/verify-baseline.yml @@ -0,0 +1,18 @@ +--- +- name: Validate the managed Narratio pipeline + hosts: localhost + gather_facts: false + tasks: + - name: Validate the pipeline as the Narratio account + become: true + ansible.builtin.command: + argv: + - /usr/bin/sudo + - -u + - narratio + - /usr/local/bin/narratio + - config + - validate + - --config + - /usr/local/etc/narratio/pipeline.yml + changed_when: false