diff --git a/ansible/inventories/group_vars/all/main.yml b/ansible/inventories/group_vars/all/main.yml index f5bc629..c30ed36 100644 --- a/ansible/inventories/group_vars/all/main.yml +++ b/ansible/inventories/group_vars/all/main.yml @@ -242,4 +242,4 @@ komodo_db_password: "{{ vault_komodo_db_password }}" komodo_webhook_secret: "{{ vault_komodo_webhook_secret }}" komodo_jwt_secret: "{{ vault_komodo_jwt_secret }}" komodo_onboarding_key: "{{ vault_komodo_onboarding_key }}" -komodo_core_address: "10.0.4.10:9120" +komodo_core_host: "10.0.4.10:9120" diff --git a/ansible/inventories/group_vars/all/vault.yml b/ansible/inventories/group_vars/all/vault.yml index 7d72a35..77020e3 100644 --- a/ansible/inventories/group_vars/all/vault.yml +++ b/ansible/inventories/group_vars/all/vault.yml @@ -1,46 +1,53 @@ $ANSIBLE_VAULT;1.1;AES256 -34633839643463656436373363623239343634393834353863336665613863663464316636343564 -6561613766613735303865363339326563363961343739370a626535346366366261666131653565 -39653832326235353662656466626132383531613338653138363039633563313536313765333166 -6265343938386332340a313230353564653739633939363031623130316265373538333237343133 -39303831303230323738383565303335363763353462353966643832626131646436336434323138 -66636635326634383137376431323736613434656533633664383161393338326565336130303930 -31303232393332623839323162373239616634356434643364323933613335363861343738313661 -63306435303962323566346564383066636634663536646264383961343461623236386362336239 -36316536346533626438366430363664383135346337303239643335656261626639323433313765 -33613564626235323337343164346235336663613238323966346331623266613832633234393232 -39393565383235373365353538373764333433386439333132643064373839643963376333373032 -34613361663630323937333162373534356263373861363163303062363232663731316164653966 -39346163353635663738316262363465643639313766346339333963333964336265303666643064 -62643366613034663838326436313739363863316666393238306564306539623162636266393033 -39653438663938346233656630366130316161373363393561663631663737373131383736666136 -66346231663338643735313739343361643431396638613837303833616539373261346163306132 -39656436626635633733396138303963343135343437313637346565643835613739653839343938 -61623561303930306335383963393561393735316331323639373536616236383532366136653134 -63366661363332366338656234386139363066363239656136383634623430303262626465356536 -34303862303763613761616134323236316232373931386336653034633230396633363965313331 -31666234363064323331666535316134623461663433366538613133396633353161323766363437 -66396439303235313137393135383837323737663731386331376362613964323737366633623733 -31353261306163356333303333393466643338343765323637656565333762386133363031396638 -34386639393133303134383034316439363262386461346534326236326135623964363461646430 -33623433306130393232353135623332396136653230383439333665303737343137383864356433 -37313262383961656133393731656162316433356336336530333862363561353238663433343131 -32353862363863343666383766363135663736613332313438356366303332326632653961313762 -34363230633461616461373536366239313962616565396566316139393466303332643265353835 -35356364343734663963303839363733393730633262643438336562643733623966663130353635 -38363833633936363164313239323265623332643539356532623636383232323538386431653165 -31353138666537616565646239303165376439373063356663316664356564323961613039663061 -62336261306532386136616465383265613262363936633164373236353238633463303939623133 -65313335653935346364383232373261666334663634373938353861383561613936393339366530 -34303961373561303031323834663466346436383966373530633430333536373833383166313938 -31376531366166333762623233646433323134663366323730613932393333333737623463653634 -31356466646465333666363034343730313164353039646236393564326536323330363639343664 -39363836663332333234396239336339356532626430653861373137333562623830653936323432 -34306238623137313232333033356266303564376163333065653137616661643537373831666136 -39303037383661313064396364363639346564643564303638623661356235336162633633616531 -34626331393564363564613937346165333936366231333462633561356362616434306165343130 -62346665653532663434396238366562336264656130643566666466313964303066373462373034 -32613434363832626265636234343535313438313532643631363633363335653364393564356632 -37613135343632613433616630333163323230333235363531343966333232646664393163386238 -36613731656666663236316133353237636363663336656162316163393230346565366536376236 -6336 +33346333343637663032363331303231316234346636353763653039383662343730356335353730 +6466653064643530393338663130613238343733646366350a666333636337356362393033393639 +30636331653939323336313638623235393632663963303834613430386135633134343465383738 +6139633438383937320a353536643230383439323339316638366464336636353164646232373839 +35323165636364363238366431383663303430316130653366643263376238316137666665376366 +61653937626264396531356639373865393962616565353265353366666133386266623638613138 +38316632363065613234306661313431663639393862316630323064376236663332313130633465 +37396136313032613932616664653239346265323135316131323735306638356664373336376231 +34376565343531393831323438303563333463373437636535636162373830386135346164333264 +37633264653932636639316161306637326361313530313365633335633937333832326130343532 +39333739313364353539313262646364323931346436393633343063666238623363646338663862 +37376565663636663166323030626133313065346135666439613032616135663136663062653032 +34616433646266313462326261323166313566386266653962663435656232303036363235373632 +32626461663936353236623564633366383366636666386236633766646439333161393438653661 +39633362313337616139373663326364353534386231636561366664363862363833613133373031 +30626664336166373938643935343138326564343036333266336331626638613238306262326261 +30666265643435633565303365623633326466653837643633653934646638383736653365653730 +66613365343730643635323032646363633934313263316534313263303533383865613636616266 +66323435363037336239643366373534653434343063623130393437393633306130356666333339 +30633533353563643832633064653165363563356436626637353833643064323432346131373230 +32346433653332623535343366306662623638326462616463376333663830663039366235313766 +31323665393964363262363366313764363965353439363865323464623632313833333235306237 +30363937646335303066343934306130313330653266383239646462636364306439636163393634 +65653638666239363737653632316536333535333134616664343665343030313637643333393963 +38306338333033313064333339393239343631643563623765663362353764323661366161636138 +30366166353364373139616562623634316432613662363539613765336335326266666666356332 +34653263316663636530323431353562643833373935306264333565363832643030646433326263 +38643462666237376331643037316232623664653333386433636566656366666430363337616130 +31383730306563386465383531366462363266333437323831393561363665663231346362363665 +35333836653931343161653261656265643732353837666337393835363536353937646561396365 +64363463326435386330343937323934356366343434323964306131363133633434303266636563 +39393463333539306262323239663033633830336433663931653433393030626633316564343737 +31626461316234623965316339343434646561636164343037356566623366626636326663623533 +63646166383835306430333965346234363334646534323566623430633865373837353763626663 +37363461656430623739366664613832346135333535393531623762656566616137303439323039 +63613436366161616263396464316231333535346561666631366464666433643031653231316464 +62626363346335663639623366396634373664643965393339636336616139616431653830306331 +37633439306365333961356336653637656661643763353032653063336430393638653830313439 +62396632316166356131316438643164386466623162663661383834346633656265396438666231 +61663332366634623032323836366235613933363964366665643330366466306434356232396330 +39636163653536396365336631366633313830633631383333383338333964613034623935666663 +66346434326630333630326333663462376431306432633466356166306532616530633262626231 +36653936333134643832343865366333623939656539386534346430646330616431376366363265 +33353132336562613139383264396462353234303533353263343433626566366263393134356666 +65343633663261626138333665323238623239363364646436336639333636663236396162376164 +62356534323663353832653466393837353931636232353331386534323934633336623039316662 +30646432613761623966623033393533633235396431353631663364653461393464353738663031 +63653931366633323966313664333562613263663239333937626536333662666538643736663034 +38316365333033333130643931333331373662326639316266363436656365646262613433376336 +31376663326563323637313339396263376231333230643238326339663239633463303435653035 +39343533636234326262323330663736326431353637343663636165663935313236316337336463 +33373033653435313235 diff --git a/ansible/playbooks/provision_komodo_stacks.yml b/ansible/playbooks/provision_komodo_stacks.yml new file mode 100644 index 0000000..8fb12c3 --- /dev/null +++ b/ansible/playbooks/provision_komodo_stacks.yml @@ -0,0 +1,45 @@ +--- +# Playbook to provision Komodo stacks from git repository +# Automatically discovers stacks from docker-compose folder structure +# Structure: docker-compose/{server}/{app}/compose.yaml +# Usage: ansible-playbook -i inventories/production.yml playbooks/provision_komodo_stacks.yml + +- name: Provision Komodo stacks + hosts: localhost + connection: local + gather_facts: false + + vars: + docker_compose_root: "{{ playbook_dir }}/../../docker-compose" + + tasks: + - name: Find all compose.yaml files in docker-compose directory + find: + paths: "{{ docker_compose_root }}" + patterns: "compose.yaml" + recurse: true + register: compose_files + + - name: Build stack list from discovered compose files + set_fact: + komodo_stacks: "{{ komodo_stacks | default([]) + [{'server': path_parts[0], 'app': path_parts[1]}] }}" + loop: "{{ compose_files.files }}" + vars: + path_parts: "{{ item.path | regex_replace('^.*/docker-compose/', '') | split('/') }}" + loop_control: + label: "{{ path_parts[0] }}/{{ path_parts[1] }}" + + - name: Display discovered stacks + debug: + msg: "Found {{ komodo_stacks | length }} stacks: {{ komodo_stacks | map(attribute='app') | list | join(', ') }}" + + - name: Create Komodo stacks + include_role: + name: komodo_stack + vars: + komodo_stack_name: "{{ item.app }}" + komodo_server_name: "{{ item.server }}" + komodo_run_directory: "docker-compose/{{ item.server }}/{{ item.app }}" + loop: "{{ komodo_stacks }}" + loop_control: + label: "{{ item.app }} (on {{ item.server }})" diff --git a/ansible/roles/komodo-periphery/templates/docker-compose.yml.j2 b/ansible/roles/komodo-periphery/templates/docker-compose.yml.j2 index 07a5027..8230fdb 100644 --- a/ansible/roles/komodo-periphery/templates/docker-compose.yml.j2 +++ b/ansible/roles/komodo-periphery/templates/docker-compose.yml.j2 @@ -5,7 +5,7 @@ services: container_name: komodo-periphery restart: unless-stopped environment: - PERIPHERY_CORE_ADDRESS: {{ komodo_core_address }} + PERIPHERY_CORE_ADDRESS: {{ komodo_core_host }} PERIPHERY_CONNECT_AS: {{ inventory_hostname }} PERIPHERY_CORE_PUBLIC_KEYS: file:/config/keys/core.pub PERIPHERY_ROOT_DIRECTORY: /etc/komodo diff --git a/ansible/roles/komodo_stack/README.md b/ansible/roles/komodo_stack/README.md new file mode 100644 index 0000000..a0f211a --- /dev/null +++ b/ansible/roles/komodo_stack/README.md @@ -0,0 +1,86 @@ +# Komodo Stack Role + +This role automates the creation of stacks in Komodo via its REST API. + +## Quick Start + +The included `provision_komodo_stacks.yml` playbook automatically discovers all stacks from your `docker-compose/` folder structure and creates them in Komodo: + +```bash +ansible-playbook -i inventories/production.yml playbooks/provision_komodo_stacks.yml +``` + +This will scan for all `compose.yaml` files in the structure: `docker-compose/{server}/{app}/compose.yaml` and create corresponding stacks in Komodo. + +## Requirements + +- A running Komodo instance with API access +- Valid Komodo API token stored in `vault_komodo_api_token` +- Existing server and repository configured in Komodo + +## Role Variables + +### Required Variables + +- `komodo_stack_name`: Name for the stack (typically the app name, e.g., "changedetection") +- `komodo_server_name`: Name of the server in Komodo where the stack should deploy +- `komodo_repo_name`: Name of the repository in Komodo containing the stack definition +- `komodo_run_directory`: Path within the repository to the compose.yaml file (e.g., `docker-compose/apps-1/changedetection`) + +### Optional Variables + +- `komodo_host`: Komodo instance URL (default: `https://komodo.{{ domain }}`) +- `komodo_api_token`: API token for authentication (default: `{{ vault_komodo_api_token }}`) + +## Example Usage + +### Automatic Discovery (Recommended) + +Use the provided playbook to automatically discover and create all stacks from your repository: + +```bash +ansible-playbook -i inventories/production.yml playbooks/provision_komodo_stacks.yml +``` + +This scans the `docker-compose/` directory and creates stacks for each app found in the structure: + +- `docker-compose/{server}/{app}/compose.yaml` + +Example structure: + +``` +docker-compose/ + apps-1/ + changedetection/ + compose.yaml + turn/ + compose.yaml + media-1/ + arr/ + compose.yaml +``` + +## How It Works + +1. Validates all required variables are provided +2. Fetches list of servers from Komodo API and finds the server ID by name +3. Fetches list of repositories from Komodo API and finds the repo ID by name +4. Checks if a stack with the same name already exists +5. Creates the stack if it doesn't exist (skips if it does) + +## API Token Setup + +Store your Komodo API token in an Ansible vault file: + +```yaml +# inventories/group_vars/all/vault.yml +vault_komodo_api_token: 'your-api-token-here' +``` + +Generate an API token in Komodo: Settings → API Keys → Create New Token + +## Notes + +- The role is idempotent - it won't create duplicate stacks +- Server and repo must already exist in Komodo before running this role +- Uses retry logic for API calls to handle temporary network issues diff --git a/ansible/roles/komodo_stack/defaults/main.yml b/ansible/roles/komodo_stack/defaults/main.yml new file mode 100644 index 0000000..ecb0d6a --- /dev/null +++ b/ansible/roles/komodo_stack/defaults/main.yml @@ -0,0 +1,13 @@ +--- +# Komodo API configuration +komodo_core_address: "http://{{ komodo_core_host }}" + +komodo_api_key: "{{ vault_komodo_api_key }}" +komodo_api_secret: "{{ vault_komodo_api_secret }}" + +# Stack configuration +komodo_repo_name: "homelab" + +# Optional stack configuration +komodo_stack_environment: [] +komodo_stack_labels: {} diff --git a/ansible/roles/komodo_stack/tasks/main.yml b/ansible/roles/komodo_stack/tasks/main.yml new file mode 100644 index 0000000..ec9c626 --- /dev/null +++ b/ansible/roles/komodo_stack/tasks/main.yml @@ -0,0 +1,128 @@ +--- +- name: Validate required variables + assert: + that: + - komodo_stack_name is defined and komodo_stack_name | length > 0 + - komodo_server_name is defined and komodo_server_name | length > 0 + - komodo_repo_name is defined and komodo_repo_name | length > 0 + - komodo_run_directory is defined and komodo_run_directory | length > 0 + - komodo_api_key is defined and komodo_api_key | length > 0 + - komodo_api_secret is defined and komodo_api_secret | length > 0 + fail_msg: "Missing required variables: komodo_stack_name, komodo_server_name, komodo_repo_name, komodo_run_directory, komodo_api_key, or komodo_api_secret" + +- name: Get Komodo server by name + uri: + url: "{{ komodo_core_address }}/read/GetServer" + method: POST + headers: + "X-Api-Key": "{{ komodo_api_key }}" + "X-Api-Secret": "{{ komodo_api_secret }}" + "Content-Type": "application/json" + body_format: json + body: + server: "{{ komodo_server_name }}" + status_code: 200 + validate_certs: false + register: komodo_server + retries: 3 + delay: 2 + +- name: Extract server ID + set_fact: + komodo_server_id: "{{ komodo_server.json._id['$oid'] }}" + +- name: Debug server response + debug: + msg: "Server ID: {{ komodo_server_id }}" + +- name: Get Komodo repo by name + uri: + url: "{{ komodo_core_address }}/read/GetRepo" + method: POST + headers: + "X-Api-Key": "{{ komodo_api_key }}" + "X-Api-Secret": "{{ komodo_api_secret }}" + "Content-Type": "application/json" + body_format: json + body: + repo: "{{ komodo_repo_name }}" + status_code: 200 + validate_certs: false + register: komodo_repo + retries: 3 + delay: 2 + +- name: Extract repo ID + set_fact: + komodo_repo_id: "{{ komodo_repo.json._id['$oid'] }}" + +- name: Debug repo response + debug: + msg: "Repo ID: {{ komodo_repo_id }}" + +- name: Check if stack already exists + uri: + url: "{{ komodo_core_address }}/read/GetStack" + method: POST + headers: + "X-Api-Key": "{{ komodo_api_key }}" + "X-Api-Secret": "{{ komodo_api_secret }}" + "Content-Type": "application/json" + body_format: json + body: + stack: "{{ komodo_stack_name }}" + status_code: [200, 500] + validate_certs: false + register: existing_stack + retries: 3 + delay: 2 + failed_when: false + +- name: Create Komodo stack + uri: + url: "{{ komodo_core_address }}/write/CreateStack" + method: POST + headers: + "X-Api-Key": "{{ komodo_api_key }}" + "X-Api-Secret": "{{ komodo_api_secret }}" + "Content-Type": "application/json" + body_format: json + body: + name: "{{ komodo_stack_name }}" + config: + server_id: "{{ komodo_server_id }}" + linked_repo: "{{ komodo_repo_id }}" + run_directory: "{{ komodo_run_directory }}" + status_code: [200, 201] + validate_certs: false + register: stack_result + when: existing_stack.status == 500 + retries: 3 + delay: 2 + +- name: Display stack creation result + debug: + msg: "Stack '{{ komodo_stack_name }}' created successfully with ID: {{ stack_result.json._id }}" + when: existing_stack.status == 500 and stack_result is succeeded + +- name: Display stack already exists message + debug: + msg: "Stack '{{ komodo_stack_name }}' already exists with ID: {{ existing_stack.json._id }}" + when: existing_stack.status == 200 + +- name: Deploy stack + uri: + url: "{{ komodo_core_address }}/execute/DeployStack" + method: POST + headers: + "X-Api-Key": "{{ komodo_api_key }}" + "X-Api-Secret": "{{ komodo_api_secret }}" + "Content-Type": "application/json" + body_format: json + body: + stack: "{{ komodo_stack_name }}" + status_code: 200 + validate_certs: false + register: start_result + retries: 3 + delay: 2