Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions files/snipeIT.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
$ANSIBLE_VAULT;1.1;AES256
30366439666465636163643962323132386136626438646536393938646339633238643730373263
3434343937636237333035373234353730313431613465350a656339666539363166636136633832
31616534353234633631356234633935323963386532353030386362623330366465623864363962
3966346136353863360a353036353435613037333734303233303236353262363734393134373562
39653930303465663838636133303434623435393764356364303865313239626439373662306439
34343239386230656232323931616134373032616234616264316237346532663935313336356430
36616138393638363364396231643032306335613632626133323265323132396664393038383465
63616662356535623762623265303837303434303630343433396566373137633431353232653566
63396532643563653264633631313535623961323661633430333436306161383131653266366331
30376631626232313633353135323966396536323661323362626261333262623132646266666232
37623034313632646435393661626361623165373435303337346531653838643162323562343065
33313930636562643637633964346339313536616138303766316534396438333630386339326531
64386637656435396331383262626537313566393234356565303432323164353861303862663262
62656464393633326236336338646139656437626163613437343231376131323633336136613933
66386538366132393463353163323863643039653361656561366438613262623161376462363039
65643363383165333666333262633236313363343532373131373466373034643463613562383738
35633362643833376462303965623236653538303065303734316161383262613132663762656331
31623134343066643265383334613866356366316630636562333666376161616665613463633563
37363437656230633037386131643834653334623337326535316430613635306337346366393962
38363966303038323365663738366530346166366465616132306537313737333432616238623831
66613637623334633236343637336561316339666239363035663034613737326133303932323161
37336538346239323232303864313265653138306132616266633661623835306161663639366637
63363736383362366661623935643135333731623664373462663137616464663535353566363361
38306438643439633966343566336533353836326537656632636635646362313363623961663166
32316233323061353930623633396361646539386236653931393839623032306230653832326461
37353065333934376665313462363130376531623237383561623937363466613136633566383262
63373566393262653234653433646337396233613163346162373235323364306565383865383865
34373161376336353233653466356262626133373130343437313130653337383835363635343263
34353162313134643731383037373264303364336238333032313932326461373631366234326665
30386235663934656536386162333234396461366436633463663236613338643038306263646232
61386261363364363734386363363434343038326230616634323765303661366639623965316631
33333265646665656165386261326331316636333461356235626166323332333336623335636430
39353238356535396538396133316338633530666162666538643438363135623664353230373835
34623062623261336234613237393234656338393135303732306130613363323430383737326331
62633662366464663733393165333039623138363365643362626366356364366439343635656230
63376164633865303330643233356336323838646335636532356662646665653131636136636234
31356433363066383463623037303532616433633230663430326137613762363131373330343135
30306566663331376235366264666433303330313162616531663533303031643434646632376632
66373566326261313236303635393333366462303863383638633530363361333031663666323931
61306538633738396461303635616132336461653237386334303134636234396133366266666366
65613565323864373332366561346138303335383134383134313834646238663437663337616566
64643039623739303661326233313936313930623238363736326362616663333265386663653939
66336532633637626535653462346230383138613033643434636364636363386531633664376332
38353139363734353739643239353866306437323565336634396536313064393865633837366233
30646163303730636461663937323230323339313836653266303133613665653764653434613238
65393961616236383435366335333736613432306563393139353961636665366539663562333335
65383331313832353464666261313230633735396231393463646335663938396435656337626563
30396563326266316361333764353633383631333034316162393166313636303131666662376333
37323161366233376231383934353861626534373831333334633539633833373336366131666533
39393736646662633030306463616432316637653162616663636535376639633137646232616564
30373335616339383762386230383330346132333263303833663332666436353033373933336165
32396666316466396439356563303036623166346563663164636231323864643964653933346339
66303566386134343535666538636162653138383362303532376363656361373431
24 changes: 21 additions & 3 deletions inventory
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
[all:vars]
ansible_ssh_user=localadmin

[workstations]
cicws01
cicws02
Expand All @@ -19,6 +16,7 @@ cicws16
cicws17
cicws18
cicws19
cicws23
cicws24
cicws25
cicws26
Expand Down Expand Up @@ -66,12 +64,32 @@ dnpws22
dnpws23
dnpws24
dnpws25
dnpws26

[computenodes]
ciccs01
ciccs02
ciccs03
ciccs04
ciccs05
ciccs06
ciccs07
ciccs08
ciccs09
ciccs10

[servers]
cicvmhost01 ansible_user=localadmin ansible_python_interpreter=/usr/bin/python3.8

[userservers]
cicus03
dnpus01

[storageservers]
cicss04 ansible_python_interpreter=/usr/bin/python3.8
cicss05
cicss06

[vms]
cloud1.douglasneuroinformatics.ca
cloud2.douglasneuroinformatics.ca
Comment on lines 81 to +95
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the dangerous thing here is accidentally running an ad-hoc command on all and touching these machines.

Can you make a separate inventory file for these to keep them isolated?

1 change: 1 addition & 0 deletions roles/common/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- {import_tasks: slurm.yml, tags: slurm}
- {import_tasks: system-config.yml, tags: system}
- {import_tasks: motd.yml, tags: motd}
- {import_tasks: snipeIT.yml, tags: snipeit}

- name: Set default locale to en_CA.UTF-8
debconf:
Expand Down
107 changes: 107 additions & 0 deletions roles/common/tasks/snipeIT.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#Fetch system facts and add them to our inventory
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add this calling of dmidecode:
How much of this is available from
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_vars_facts.html ?

regarding dmidecode in general, might be be more reliable to use jc?
https://kellyjonbrazil.github.io/jc/docs/parsers/dmidecode.html

Similarly, lshw can output json we can parse.

- name: Retrieve BIOS vendor
command: dmidecode -s bios-vendor
register: bios_vendor
ignore_errors: yes

- name: Retrieve BIOS version
command: dmidecode -s bios-version
register: bios_version
ignore_errors: yes

- name: Retrieve BIOS revision
command: dmidecode -s bios-revision
register: bios_revision
ignore_errors: yes

Comment on lines +2 to +16
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Run hardware probes with privilege; mark them idempotent.

dmidecode/lshw need root. Also mark these command tasks changed_when: false to avoid noisy diffs.

Apply this diff pattern to each command task:

-      command: dmidecode -s bios-vendor
+      command: dmidecode -s bios-vendor
+      become: true
+      changed_when: false
...
-      command: lshw -C display
+      command: lshw -C display
+      become: true
+      changed_when: false

Also applies to: 21-28, 33-36, 41-52, 57-60

🤖 Prompt for AI Agents
In roles/common/tasks/snipeIT.yml around lines 2 to 16, the dmidecode command
tasks must run with elevated privileges and be marked idempotent: add become:
yes to ensure the probe runs as root and add changed_when: false to prevent
Ansible from reporting a change every run (keep existing register and
ignore_errors as-is); apply the same pattern (become: yes and changed_when:
false) to the other command tasks at lines 21-28, 33-36, 41-52, and 57-60.

- name: Set BIOS info
set_fact:
bios_version: "{{ bios_vendor.stdout | default('') | trim }} {{ bios_version.stdout | default('') | trim }} {{ bios_revision.stdout | default('') | trim }}"

- name: Retrieve baseboard manufacturer
command: dmidecode -s baseboard-manufacturer
register: baseboard_manufacturer

- name: Retrieve baseboard product name
command: dmidecode -s baseboard-product-name
register: baseboard_product_name

- name: Set motherboard info
set_fact:
motherboard: "{{ baseboard_manufacturer.stdout | trim }} {{ baseboard_product_name.stdout | trim }}"

- name: Retrieve baseboard serial number
command: dmidecode -s baseboard-serial-number
register: baseboard_serial_number

- name: Set serial number
set_fact:
serial_number: "{{ baseboard_serial_number.stdout | trim }}"

- name: Retrieve cpu type
command: dmidecode -s processor-family
register: processor_family

- name: Retrieve cpu manufacturer
command: dmidecode -s processor-manufacturer
register: processor_manufacturer

- name: Retrieve cpu version
command: dmidecode -s processor-version
register: processor_version

- name: Set cpu info
set_fact:
cpu: "{{ processor_family.stdout | trim }} {{ processor_manufacturer.stdout | trim }} {{ processor_version.stdout | trim }}"

- name: Retrieve gpu
command: lshw -C display
register: gpu_info
failed_when: "'command not found' in gpu_info.stderr"

- name: Parse gpu model
set_fact:
gpu_model: "{{ gpu_info.stdout | regex_search('product: (.+)', '\\1') | default('Unknown') | replace('[', '') | replace(']', '') | replace('\"', '') | replace(\"'\", '') }}"

- name: Determine model_id based on inventory group
set_fact:
model_id: >-
{% if 'workstations' in group_names %}1
{% elif 'computenodes' in group_names %}3
{% elif 'vms' in group_names %}4
{% elif 'userservers' in group_names %}5
{% elif 'storageservers' in group_names %}6
{% elif 'servers' in group_names %}12
{% else %}13 # Default model ID if not matched
{% endif %}
- name: Create asset in Snipe-IT
uri:
url: "https://inventory.douglasneuroinformatics.ca/api/v1/hardware"
method: POST
headers:
Authorization: "Bearer {{ lookup('file', 'files/snipeIT.key') }}"
Accept: "application/json"
Content-Type: "application/json"
body_format: json
Comment on lines +78 to +86
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don't log the Snipe‑IT API token; use a vaulted var instead of file lookup.

  • Headers may be printed in logs; the Bearer token would leak.
  • lookup('file', 'files/snipeIT.key') won’t auto‑decrypt an Ansible Vault file and will likely send ciphertext, breaking auth.

Apply this diff to hide secrets and use a vaulted variable (define snipeit_api_token in group_vars/all/vault.yml or the role’s defaults/vars as vaulted):

-    - name: Create asset in Snipe-IT
+    - name: Create asset in Snipe-IT
+      no_log: true
       uri:
         url: "https://inventory.douglasneuroinformatics.ca/api/v1/hardware"
         method: POST
         headers:
-          Authorization: "Bearer {{ lookup('file', 'files/snipeIT.key') }}"
+          Authorization: "Bearer {{ snipeit_api_token }}"
           Accept: "application/json"
           Content-Type: "application/json"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Create asset in Snipe-IT
uri:
url: "https://inventory.douglasneuroinformatics.ca/api/v1/hardware"
method: POST
headers:
Authorization: "Bearer {{ lookup('file', 'files/snipeIT.key') }}"
Accept: "application/json"
Content-Type: "application/json"
body_format: json
- name: Create asset in Snipe-IT
no_log: true
uri:
url: "https://inventory.douglasneuroinformatics.ca/api/v1/hardware"
method: POST
headers:
Authorization: "Bearer {{ snipeit_api_token }}"
Accept: "application/json"
Content-Type: "application/json"
body_format: json
🤖 Prompt for AI Agents
In roles/common/tasks/snipeIT.yml around lines 78-86, replace the insecure
lookup('file', 'files/snipeIT.key') usage in the Authorization header with a
vaulted variable (e.g. Authorization: "Bearer {{ snipeit_api_token }}") and add
no_log: true to the task to prevent the headers/body from being printed in logs;
also ensure snipeit_api_token is defined in a Vault file (e.g.
group_vars/all/vault.yml or the role’s vaulted vars) and remove the file lookup
so ciphertext is not sent.

body:
name: "{{ ansible_hostname }}"
asset_tag: "{{ serial_number }}"
serial: "{{ serial_number }}"
model_id: "{{ model_id }}"
status_id: 4 #Online status
_snipeit_bios_15: "{{ bios_version }}"
_snipeit_motherboard_16: "{{ motherboard }}"
_snipeit_os_8: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
_snipeit_ram_10: "{{ ansible_memtotal_mb }} MB"
_snipeit_cpu_11: "{{ cpu }}"
_snipeit_gpu_9: "{{ gpu_model }}"
_snipeit_ip_address_12: "{{ ansible_all_ipv4_addresses | join(', ') }}"
_snipeit_mac_address_1: "{{ ansible_default_ipv4.macaddress }}"
_snipeit_location_nickname_3: "unknown"
_snipeit_primary_user_5: "unknown"
_snipeit_purchase_date_aprox_6: "unknown"
_snipeit_owner_4: "unknown"
_snipeit_service_status_13: "in service"
status_code: [200, 201]
delegate_to: localhost
Comment on lines +78 to +107
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Make the call idempotent (search → create or update) to avoid duplicate assets.

Current POST will fail on reruns if asset_tag/serial already exists (Snipe‑IT returns 4xx). Query first, then POST or PUT.

Here’s a minimal replacement for the POST task:

-    - name: Create asset in Snipe-IT
-      uri:
-        url: "https://inventory.douglasneuroinformatics.ca/api/v1/hardware"
-        method: POST
-        headers:
-          Authorization: "Bearer {{ snipeit_api_token }}"
-          Accept: "application/json"
-          Content-Type: "application/json"
-        body_format: json
-        body:
-          name: "{{ ansible_hostname }}"
-          asset_tag: "{{ serial_number }}"
-          serial: "{{ serial_number }}"
-          model_id: "{{ model_id }}"
-          status_id: 4  #Online status
-          _snipeit_bios_15: "{{ bios_version }}"
-          _snipeit_motherboard_16: "{{ motherboard }}"
-          _snipeit_os_8: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
-          _snipeit_ram_10: "{{ ansible_memtotal_mb }} MB"
-          _snipeit_cpu_11: "{{ cpu }}"
-          _snipeit_gpu_9: "{{ gpu_model }}"
-          _snipeit_ip_address_12: "{{ ansible_all_ipv4_addresses | join(', ') }}"
-          _snipeit_mac_address_1: "{{ ansible_default_ipv4.macaddress }}"
-          _snipeit_location_nickname_3: "unknown"
-          _snipeit_primary_user_5: "unknown"
-          _snipeit_purchase_date_aprox_6: "unknown"
-          _snipeit_owner_4: "unknown"
-          _snipeit_service_status_13: "in service"
-        status_code: [200, 201]
-      delegate_to: localhost
+    - name: Lookup existing asset by serial/asset_tag
+      no_log: true
+      uri:
+        url: "https://inventory.douglasneuroinformatics.ca/api/v1/hardware?search={{ serial_number | urlencode }}"
+        method: GET
+        headers:
+          Authorization: "Bearer {{ snipeit_api_token }}"
+          Accept: "application/json"
+        return_content: true
+        status_code: 200
+        timeout: 30
+      register: snipeit_search
+      delegate_to: localhost
+
+    - name: Set existing asset id (if any)
+      set_fact:
+        snipeit_existing_id: "{{ (snipeit_search.json.rows | default([]) | selectattr('serial','equalto', serial_number) | list | first).id | default(omit) }}"
+
+    - name: Create asset in Snipe-IT (POST)
+      no_log: true
+      uri:
+        url: "https://inventory.douglasneuroinformatics.ca/api/v1/hardware"
+        method: POST
+        headers:
+          Authorization: "Bearer {{ snipeit_api_token }}"
+          Accept: "application/json"
+          Content-Type: "application/json"
+        body_format: json
+        body: "{{ snipeit_body }}"
+        status_code: [201]
+        timeout: 30
+      when: snipeit_existing_id is not defined
+      delegate_to: localhost
+
+    - name: Update asset in Snipe-IT (PUT)
+      no_log: true
+      uri:
+        url: "https://inventory.douglasneuroinformatics.ca/api/v1/hardware/{{ snipeit_existing_id }}"
+        method: PUT
+        headers:
+          Authorization: "Bearer {{ snipeit_api_token }}"
+          Accept: "application/json"
+          Content-Type: "application/json"
+        body_format: json
+        body: "{{ snipeit_body }}"
+        status_code: [200]
+        timeout: 30
+      when: snipeit_existing_id is defined
+      delegate_to: localhost

Add once above these tasks:

- name: Compose Snipe-IT body
  set_fact:
    snipeit_body:
      name: "{{ ansible_hostname }}"
      asset_tag: "{{ serial_number }}"
      serial: "{{ serial_number }}"
      model_id: "{{ model_id }}"
      status_id: 4
      _snipeit_bios_15: "{{ bios_info }}"
      _snipeit_motherboard_16: "{{ motherboard }}"
      _snipeit_os_8: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
      _snipeit_ram_10: "{{ ansible_memtotal_mb }} MB"
      _snipeit_cpu_11: "{{ cpu }}"
      _snipeit_gpu_9: "{{ gpu_model }}"
      _snipeit_ip_address_12: "{{ ansible_all_ipv4_addresses | join(', ') | default('') }}"
      _snipeit_mac_address_1: "{{ ansible_default_ipv4.macaddress | default('') }}"
      _snipeit_location_nickname_3: "unknown"
      _snipeit_primary_user_5: "unknown"
      _snipeit_purchase_date_aprox_6: "unknown"
      _snipeit_owner_4: "unknown"
      _snipeit_service_status_13: "in service"

25 changes: 25 additions & 0 deletions site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,28 @@
roles:
- bodsch.snapd
- common

- hosts: servers
become: yes
roles:
- bodsch.snapd
- common

- hosts: userservers
become: yes
roles:
- bodsch.snapd
- common

- hosts: storageservers
become: yes
roles:
- bodsch.snapd
# - common

- hosts: vms
become: yes
roles:
- bodsch.snapd
# - common

Comment on lines +17 to +41
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we should be running the nosnap stuff on the servers.

5 changes: 5 additions & 0 deletions update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
- hosts:
- workstations
- computenodes
- servers
- userservers
- storageservers
- vms
Comment on lines +5 to +8
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Expanding this maintenance play to servers/storage/vms may be risky.

This play runs kernel/NVIDIA install, initramfs, grub updates, and reboots. That’s often undesirable on storage servers and VMs.

  • Gate GPU/kernel tasks to workstations/computenodes:
-    - name: Install kernel and nvidia and hold packages
-      ansible.builtin.script: bin/install_kernel.sh
-      when: numusers.stdout_lines | length < 2
+    - name: Install kernel and nvidia and hold packages
+      ansible.builtin.script: bin/install_kernel.sh
+      when:
+        - numusers.stdout_lines | length < 2
+        - "'workstations' in group_names or 'computenodes' in group_names"
  • Consider splitting this into separate plays per host class with tailored tasks.

🏁 Script executed:

#!/bin/bash
# Quick check: tasks that may be unsafe for servers/vms
rg -n "install_kernel\.sh|update-initramfs|update-grub|reboot" -n -C1 update.yml

Length of output: 448


Gate kernel/GRUB/initramfs/reboot tasks to compute/workstation hosts

update-initramfs and update-grub run unguarded; install_kernel.sh (lines 22–24) and reboot (48–51) are only conditioned on numusers — expanding this play to servers/storageservers/vms will run risky operations on storage servers and VMs.

  • Findings (update.yml): install_kernel.sh — 22–24; update-initramfs — 35–36 (no guard); update-grub — 38–39 (no guard); reboot — 48–51.
  • Fix: add host-group guards (e.g., workstations / computenodes) to the kernel install, initramfs, grub, and reboot tasks. Example (original diff) for the kernel install — apply the same pattern to initramfs, grub, and reboot where appropriate:
-    - name: Install kernel and nvidia and hold packages
-      ansible.builtin.script: bin/install_kernel.sh
-      when: numusers.stdout_lines | length < 2
+    - name: Install kernel and nvidia and hold packages
+      ansible.builtin.script: bin/install_kernel.sh
+      when:
+        - numusers.stdout_lines | length < 2
+        - "'workstations' in group_names or 'computenodes' in group_names"
  • Also consider splitting this play into separate plays per host class with tailored tasks.

Committable suggestion skipped: line range outside the PR's diff.


become: yes
tasks:
- name: Gather logged in user
Expand Down