diff --git a/.typos.toml b/.typos.toml index 029b7d36f..ee031846e 100644 --- a/.typos.toml +++ b/.typos.toml @@ -6,7 +6,8 @@ extend-exclude = [ "schema/argo-workflows.json", "python/understack-workflows/tests/json_samples/", "containers/*/patches", - "go.mod" + "go.mod", + "ansible/roles/statuses/defaults/main.yaml", ] [default] @@ -19,3 +20,4 @@ extend-ignore-identifiers-re = [ HPE = "HPE" fo = "fo" sme = "sme" +4caf50 = "4caf50" diff --git a/ansible/playbooks/nautobot-initial-setup.yaml b/ansible/playbooks/nautobot-initial-setup.yaml new file mode 100644 index 000000000..d07dc7bad --- /dev/null +++ b/ansible/playbooks/nautobot-initial-setup.yaml @@ -0,0 +1,52 @@ +--- +- name: Initial setup of a Nautobot Dev Instance + connection: local + hosts: nautobot + gather_facts: false + + # The goal after this playbook runs is to have a set of devices that can + # be tested against. For a Device to be defined in Nautobot you must + # first have a Device Role, a Device Type, and a Rack. For a Device Type + # to be defined you must first have a Manufacturer. For a Rack to be + # defined you must first have a Site. The Rackspace plugin to read + # data from CORE and Location Manager can provide some of these but + # that involves enabling Jobs from our plugin first. To be able to + # run our Jobs we need to populate some Secrets as well. + # + # To visualized linearly we need to instanitate the following: + # - Jobs + # - Secrets + # - Locations + # - Racks + # - Tenants + # - Device Role + # - Manufacturer + # - Device Type + # - Devices + + pre_tasks: + - name: Ensure nautobot is up and responding + ansible.builtin.uri: + url: "{{ nautobot_url }}/health/" + method: GET + validate_certs: false + register: nautobot_up_check + until: nautobot_up_check.status == 200 + retries: 24 # Retries for 24 * 5 seconds = 120 seconds = 2 minutes + delay: 5 # Every 5 seconds + check_mode: false + + roles: + - role: permissions + tags: permissions + - role: jobs + - role: secrets + - role: git_repos + - role: location_types + - role: locations + - role: roles + - role: statuses + - role: custom_fields + - role: computed_fields + - role: platforms + - role: relationships diff --git a/ansible/roles/computed_fields/README.md b/ansible/roles/computed_fields/README.md new file mode 100644 index 000000000..ba22572dc --- /dev/null +++ b/ansible/roles/computed_fields/README.md @@ -0,0 +1,17 @@ +# computed fields dynamically create a new field pulling from existing data sources already in Nautobot, + +In your variables you'll need to define something like: + +```yaml +computed_fields_data: + - key: urn + display: "Device URN" + content_type: dcim.device + label: "Device URN" + description: "A string representing the URN for a device" + template: "{%- if obj.serial and obj.role and obj.location and obj.device_type and obj.device_type.manufacturer %}\n {%- set role = obj.role.name | lower | replace(\" \", \"-\") -%}\n {%- set loc = obj.location.name | lower -%}\n {%- set manufacturer = obj.device_type.manufacturer.name | lower -%}\n {%- set serial = obj.serial | lower -%}\n {%- set partition = \"UC_PARTITION\" | settings_or_config -%}\nurn:rax:undercloud:{{ partition }}:nautobot:{{ role }}:{{ loc }}:{{ manufacturer }}-{{ serial }} {%- endif %}" + weight: 100 + advanced_ui: false +``` + +Where the `key` is the unique slug. diff --git a/ansible/roles/computed_fields/defaults/main.yml b/ansible/roles/computed_fields/defaults/main.yml new file mode 100644 index 000000000..2d75f2b5f --- /dev/null +++ b/ansible/roles/computed_fields/defaults/main.yml @@ -0,0 +1,2 @@ +--- +computed_fields_data: [] diff --git a/ansible/roles/computed_fields/tasks/computed_fields.yml b/ansible/roles/computed_fields/tasks/computed_fields.yml new file mode 100644 index 000000000..736a18084 --- /dev/null +++ b/ansible/roles/computed_fields/tasks/computed_fields.yml @@ -0,0 +1,27 @@ +--- + +- name: Check if compute field exists + ansible.builtin.uri: + url: "{{ nautobot_url }}/api/extras/computed-fields/?key={{ item.key }}" + method: GET + body_format: json + headers: + Accept: application/json; version={{ nautobot_api_version }} + Authorization: "Token {{ nautobot_token }}" + register: _computed_field + check_mode: false + +- name: Create/update computed fields + ansible.builtin.uri: + url: "{{ nautobot_url }}/api/extras/computed-fields/{{ url_append }}" + method: "{{ method }}" + headers: + Accept: application/json; version={{ nautobot_api_version }} + Authorization: "Token {{ nautobot_token }}" + body_format: json + body: "{{ item }}" + status_code: "{{ status_code }}" + vars: + url_append: "{{ _computed_field.json.results[0].id + '/' if _computed_field.json.count == 1 else '' }}" + method: "{{ 'PATCH' if _computed_field.json.count == 1 else 'POST' }}" + status_code: "{{ 200 if _computed_field.json.count == 1 else 201 }}" diff --git a/ansible/roles/computed_fields/tasks/main.yml b/ansible/roles/computed_fields/tasks/main.yml new file mode 100644 index 000000000..4d20599d2 --- /dev/null +++ b/ansible/roles/computed_fields/tasks/main.yml @@ -0,0 +1,5 @@ +--- + +- name: Define computed fields + ansible.builtin.include_tasks: computed_fields.yml + loop: "{{ computed_fields_data }}" diff --git a/ansible/roles/custom_field_choices/defaults/main.yml b/ansible/roles/custom_field_choices/defaults/main.yml new file mode 100644 index 000000000..a056ba1c1 --- /dev/null +++ b/ansible/roles/custom_field_choices/defaults/main.yml @@ -0,0 +1,107 @@ +--- + +custom_field_choices_data: + - value: active + weight: 100 + custom_field: ironic_provision_state + state: present + - value: adopting + weight: 100 + custom_field: ironic_provision_state + state: present + - value: available + weight: 100 + custom_field: ironic_provision_state + state: present + - value: cleaning + weight: 100 + custom_field: ironic_provision_state + state: present + - value: clean wait + weight: 100 + custom_field: ironic_provision_state + state: present + - value: deleting + weight: 100 + custom_field: ironic_provision_state + state: present + - value: deploy failed + weight: 100 + custom_field: ironic_provision_state + state: present + - value: deploying + weight: 100 + custom_field: ironic_provision_state + state: present + - value: enroll + weight: 100 + custom_field: ironic_provision_state + state: present + - value: error + weight: 100 + custom_field: ironic_provision_state + state: present + - value: inspect failed + weight: 100 + custom_field: ironic_provision_state + state: present + - value: inspecting + weight: 100 + custom_field: ironic_provision_state + state: present + - value: inspect wait + weight: 100 + custom_field: ironic_provision_state + state: present + - value: manageable + weight: 100 + custom_field: ironic_provision_state + state: present + - value: rescue + weight: 100 + custom_field: ironic_provision_state + state: present + - value: rescue failed + weight: 100 + custom_field: ironic_provision_state + state: present + - value: rescue wait + weight: 100 + custom_field: ironic_provision_state + state: present + - value: rescuing + weight: 100 + custom_field: ironic_provision_state + state: present + - value: rescuingrescue wait + weight: 100 + custom_field: ironic_provision_state + state: present + - value: service failed + weight: 100 + custom_field: ironic_provision_state + state: present + - value: service wait + weight: 100 + custom_field: ironic_provision_state + state: present + - value: servicing + weight: 100 + custom_field: ironic_provision_state + state: present + - value: unrescue failed + weight: 100 + custom_field: ironic_provision_state + state: present + - value: unrescuing + weight: 100 + custom_field: ironic_provision_state + state: present + - value: verifying + weight: 100 + custom_field: ironic_provision_state + state: present + - value: wait call-back + weight: 100 + custom_field: ironic_provision_state + state: present diff --git a/ansible/roles/custom_field_choices/tasks/main.yml b/ansible/roles/custom_field_choices/tasks/main.yml new file mode 100644 index 000000000..72e6c7042 --- /dev/null +++ b/ansible/roles/custom_field_choices/tasks/main.yml @@ -0,0 +1,12 @@ +--- + +- name: Create Custom Field Choices + networktocode.nautobot.custom_field_choice: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + value: "{{ item.value }}" + weight: "{{ item.weight }}" + custom_field: "{{ item.custom_field }}" + state: present + + loop: "{{ custom_field_choices_data }}" diff --git a/ansible/roles/custom_fields/tasks/main.yml b/ansible/roles/custom_fields/tasks/main.yml new file mode 100644 index 000000000..51b2bd3b8 --- /dev/null +++ b/ansible/roles/custom_fields/tasks/main.yml @@ -0,0 +1,57 @@ +--- +- name: Create Custom Field Chassis MAC Address + networktocode.nautobot.custom_field: + state: present + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + description: MAC address advertised to LLDP neighbors (for switches only) + label: Chassis MAC Address + type: text + key: chassis_mac_address + required: false + weight: 100 + content_types: dcim.device + filter_logic: exact + validation_regex: ^[0-9A-F][0-9A-F](:[0-9A-F][0-9A-F]){5}$ + +- name: Create Custom Field DHCP Relay IPv4 Address + networktocode.nautobot.custom_field: + state: present + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + description: >- + For certain interface Roles, configure a "DHCP Helper" service on the + router to forward DHCP requests from locally-connected clients to the + DHCP server specified by this option. The value is an IPv4 address + without the /prefixlen. + label: DHCP Relay IPv4 Address + type: text + key: dhcp_relay_ipv4_address + required: false + weight: 100 + content_types: + - dcim.interface + - vni_custom_model.ucvni + validation_regex: ^\d+\.\d+\.\d+\.\d+$ + filter_logic: exact + +- name: Create Custom Field Tenant VLAN ID + networktocode.nautobot.custom_field: + state: present + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + description: >- + VLAN ID visible to the Tenant, used in the dot1q tag when this VNI + appears on a trunk port. Normally different to the actual VLAN created + on the switch - we use VLAN translation to gives the tenant a consistent + VLAN ID across the whole fabric. + label: Tenant VLAN ID + type: integer + key: tenant_vlan_id + required: false + weight: 100 + content_types: + - vni_custom_model.ucvni + validation_minimum: 1 + validation_maximum: 4096 + filter_logic: exact diff --git a/ansible/roles/git_repos/README.md b/ansible/roles/git_repos/README.md new file mode 100644 index 000000000..593ddb9d3 --- /dev/null +++ b/ansible/roles/git_repos/README.md @@ -0,0 +1,15 @@ +# Adding Git Repos + +In your variables you'll need to define something like: + +```yaml +nb_git_repos: + device_types: + name: Device Types + remote_url: https://github.com/RSS-Engineering/undercloud-nautobot-device-types.git + branch: main + secrets_group: gh_dev_type_pat +``` + +Where the `key` is the unique slug and the `secrets_group` is the reference to +a `secrets_group` you've defined in the variable `nb_secrets_groups`. diff --git a/ansible/roles/git_repos/defaults/main.yaml b/ansible/roles/git_repos/defaults/main.yaml new file mode 100644 index 000000000..527d985c6 --- /dev/null +++ b/ansible/roles/git_repos/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +git_repos_nb_git_repos: {} diff --git a/ansible/roles/git_repos/tasks/git_repo_def.yml b/ansible/roles/git_repos/tasks/git_repo_def.yml new file mode 100644 index 000000000..9d2751867 --- /dev/null +++ b/ansible/roles/git_repos/tasks/git_repo_def.yml @@ -0,0 +1,28 @@ +--- + +- name: Check if git repo exists + ansible.builtin.uri: + url: "{{ nautobot_url }}/api/extras/git-repositories/?slug={{ item.key }}" + method: GET + body_format: json + headers: + Accept: "application/json; version={{ nautobot_api_version }}" + Authorization: "Token {{ nautobot_token }}" + register: _secret + check_mode: false + +- name: Create/update secret group association + ansible.builtin.uri: + url: "{{ nautobot_url }}/api/extras/git-repositories/{{ url_append }}" + method: "{{ method }}" + headers: + Accept: "application/json; version={{ nautobot_api_version }}" + Authorization: "Token {{ nautobot_token }}" + body_format: json + body: "{{ item.value | combine({'slug': item.key}) }}" + status_code: "{{ status_code }}" + register: _secret_create + vars: + url_append: "{{ _secret.json.results[0].id + '/' if _secret.json.count == 1 else '' }}" + method: "{{ 'PATCH' if _secret.json.count == 1 else 'POST' }}" + status_code: "{{ 200 if _secret.json.count == 1 else 201 }}" diff --git a/ansible/roles/git_repos/tasks/main.yml b/ansible/roles/git_repos/tasks/main.yml new file mode 100644 index 000000000..3acceb376 --- /dev/null +++ b/ansible/roles/git_repos/tasks/main.yml @@ -0,0 +1,5 @@ +--- + +- name: Define git repos + ansible.builtin.include_tasks: git_repo_def.yml + loop: "{{ git_repos_nb_git_repos | dict2items }}" diff --git a/ansible/roles/jobs/tasks/main.yml b/ansible/roles/jobs/tasks/main.yml new file mode 100644 index 000000000..62ddefaf3 --- /dev/null +++ b/ansible/roles/jobs/tasks/main.yml @@ -0,0 +1,23 @@ +--- + +- name: "Lookup Rackspace Jobs" + ansible.builtin.uri: + url: "{{ nautobot_url }}/api/extras/jobs/" + method: GET + headers: + Authorization: "Token {{ nautobot_token }}" + register: rackspace_jobs + check_mode: false + +- name: "Enable Rackspace Jobs" + ansible.builtin.uri: + url: "{{ nautobot_url }}/api/extras/jobs/{{ item.id }}/" + method: PATCH + headers: + Authorization: "Token {{ nautobot_token }}" + body_format: json + body: + enabled: true + loop: "{{ rackspace_jobs.json | community.general.json_query(rackspace_grouping_query) }}" + vars: + rackspace_grouping_query: "results[?grouping=='Rackspace' || grouping=='ServiceNow Sync Jobs']" diff --git a/ansible/roles/location_types/defaults/main.yml b/ansible/roles/location_types/defaults/main.yml new file mode 100644 index 000000000..53f16eb29 --- /dev/null +++ b/ansible/roles/location_types/defaults/main.yml @@ -0,0 +1,27 @@ +--- + +location_types_defaults: + - name: Company + description: Company + content_types: + - "ipam.namespace" + nestable: false + parent: Global + - name: Region + description: Region + content_types: [] + nestable: false + parent: Company + - name: Site + description: Site + content_types: + - "dcim.device" + - "dcim.rack" + - "ipam.prefix" + - "ipam.vlan" + - "dcim.rackgroup" + - "ipam.vlangroup" + - "ipam.namespace" + - "vni_custom_model.ucvnigroup" + nestable: false + parent: Region diff --git a/ansible/roles/location_types/tasks/main.yml b/ansible/roles/location_types/tasks/main.yml new file mode 100644 index 000000000..7f8d90e2f --- /dev/null +++ b/ansible/roles/location_types/tasks/main.yml @@ -0,0 +1,23 @@ +--- + +- name: Create Global location type which has no parent + networktocode.nautobot.location_type: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: "Global" + description: "Global" + content_types: [] + nestable: "false" + state: present + +- name: Create default location types which are nested + networktocode.nautobot.location_type: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: "{{ item.name }}" + description: "{{ item.description }}" + content_types: "{{ item.content_types }}" + nestable: "{{ item.nestable }}" + parent: "{{ item.parent }}" + state: present + loop: "{{ location_types_defaults }}" diff --git a/ansible/roles/locations/tasks/main.yml b/ansible/roles/locations/tasks/main.yml new file mode 100644 index 000000000..4a6433a4c --- /dev/null +++ b/ansible/roles/locations/tasks/main.yml @@ -0,0 +1,25 @@ +--- + +# Create a "Global" location which has no parent and will be +# the parent for all other locations. +# Note: there's no 'parent_location' in this block +- name: Create Global Location + networktocode.nautobot.location: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: "Global" + description: "Global" + location_type: "Global" + status: "Active" + state: present + +- name: Create Rackspace Location + networktocode.nautobot.location: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: "Rackspace" + description: "Rackspace" + location_type: "Company" + parent_location: "Global" + status: "Active" + state: present diff --git a/ansible/roles/permissions/defaults/main.yml b/ansible/roles/permissions/defaults/main.yml new file mode 100644 index 000000000..66f3a0702 --- /dev/null +++ b/ansible/roles/permissions/defaults/main.yml @@ -0,0 +1,125 @@ +--- +permissions_groups: + - name: ucadmin + - name: ucdctech + - name: ucneteng + - name: ucuser + +permissions_permissions: + # escape hatch permission allowing ucadmin members + # to edit their own user to grant themselves superuser + - name: all-user-defaults + description: permissions granted to all users by default + enabled: true + groups: + - ucadmin + - ucdctech + - ucneteng + - ucuser + actions: + - view + object_types: + - dcim.cable + - dcim.device + - dcim.rack + - ipam.ipaddress + - ipam.prefix + - dcim.interface + - dcim.location + - extras.role + - dcim.deviceredundancygroup + - dcim.devicetype + - vni_custom_model.ucvni + - tenancy.tenant + - dcim.frontport + - dcim.manufacturer + - dcim.platform + - dcim.rackgroup + - dcim.locationtype + - extras.tag + - extras.status + - extras.objectchange + - extras.note + - tenancy.tenantgroup + - vni_custom_model.ucvnigroup + - name: all-users-api-tokens + description: allow users to manage their own API tokens + enabled: true + groups: + - ucuser + actions: + - add + - change + - delete + object_types: + - users.token + constraints: + - user: "$user" + - name: ucadmin-defaults + description: default ucadmin permissions + actions: + - view + enabled: true + groups: + - ucadmin + object_types: + - circuits.circuit + - dcim.powerfeed + - ipam.vlan + - dcim.interfaceredundancygroup + - dcim.inventoryitem + - circuits.circuittermination + - circuits.circuittype + - circuits.provider + - circuits.providernetwork + - dcim.cablepath + - dcim.consoleport + - dcim.devicebay + - dcim.poweroutlet + - dcim.powerpanel + - dcim.powerport + - dcim.rearport + - dcim.virtualchassis + - ipam.rir + - ipam.routetarget + - ipam.vrf + - ipam.vlangroup + - ipam.service + - ipam.ipaddresstointerface + - ipam.vrfprefixassignment + - ipam.vrfdeviceassignment + - ipam.namespace + - name: ucdctech-defaults + description: default ucdctech permissions + actions: + - view + enabled: true + groups: + - ucdctech + object_types: + - circuits.circuit + - dcim.powerfeed + - ipam.vlan + - dcim.interfaceredundancygroup + - dcim.inventoryitem + - circuits.circuittermination + - circuits.circuittype + - circuits.provider + - circuits.providernetwork + - dcim.cablepath + - dcim.consoleport + - dcim.devicebay + - dcim.poweroutlet + - dcim.powerpanel + - dcim.powerport + - dcim.rearport + - dcim.virtualchassis + - ipam.rir + - ipam.routetarget + - ipam.vrf + - ipam.vlangroup + - ipam.service + - ipam.ipaddresstointerface + - ipam.vrfprefixassignment + - ipam.vrfdeviceassignment + - ipam.namespace diff --git a/ansible/roles/permissions/tasks/main.yml b/ansible/roles/permissions/tasks/main.yml new file mode 100644 index 000000000..d712d793d --- /dev/null +++ b/ansible/roles/permissions/tasks/main.yml @@ -0,0 +1,27 @@ +--- + +- name: Set up nautobot groups + networktocode.nautobot.admin_group: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: "{{ item.name }}" + state: present + loop: "{{ permissions_groups }}" + loop_control: + label: "{{ item.name }}" + +- name: Set up nautobot permissions + networktocode.nautobot.admin_permission: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: "{{ item.name }}" + description: "{{ item.description }}" + enabled: "{{ item.enabled }}" + actions: "{{ item.actions }}" + object_types: "{{ item.object_types }}" + groups: "{{ item.groups }}" + constraints: "{{ item.constraints | default('') }}" + state: present + loop: "{{ permissions_permissions }}" + loop_control: + label: "{{ item.name }}" diff --git a/ansible/roles/platforms/defaults/main.yml b/ansible/roles/platforms/defaults/main.yml new file mode 100644 index 000000000..d8c6bb8d4 --- /dev/null +++ b/ansible/roles/platforms/defaults/main.yml @@ -0,0 +1,21 @@ +--- + +platforms_defaults: + - man: Arista + name: "Arista EOS" + driver: eos + - man: Cisco + name: "Cisco IOS" + driver: ios + - man: Dell + name: "Dell iDRAC9" + driver: idrac9 + - man: HPE + name: "HPE iLO5" + driver: ilo5 + +platforms_with_drivers: + - man: Cisco + name: "Cisco NX-OS" + driver: nxos_ssh + network: cisco_nxos diff --git a/ansible/roles/platforms/tasks/main.yml b/ansible/roles/platforms/tasks/main.yml new file mode 100644 index 000000000..ab09e3539 --- /dev/null +++ b/ansible/roles/platforms/tasks/main.yml @@ -0,0 +1,23 @@ +--- + +- name: Create Default Platforms + networktocode.nautobot.platform: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: "{{ item.name }}" + manufacturer: "{{ item.man }}" + napalm_driver: "{{ item.driver }}" + state: present + loop: "{{ platforms_defaults }}" + +- name: Create Default Platforms with Network Drivers + # Will need to include the nxos network driver for n9ks when ansible supports it + networktocode.nautobot.platform: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: "{{ item.name }}" + manufacturer: "{{ item.man }}" + napalm_driver: "{{ item.driver }}" + # network_driver: "{{ item.network }}" + state: present + loop: "{{ platforms_with_drivers }}" diff --git a/ansible/roles/relationships/defaults/main.yml b/ansible/roles/relationships/defaults/main.yml new file mode 100644 index 000000000..12afc8b0b --- /dev/null +++ b/ansible/roles/relationships/defaults/main.yml @@ -0,0 +1,27 @@ +--- + +relationships_defaults: + - name: "Namespace to Tenant" + source_type: "tenancy.tenant" + source_label: "Namespace Name" + source_hidden: false + type: "one-to-many" + destination_type: "ipam.namespace" + destination_label: "Tenant Name" + destination_hidden: true + - name: "Prefix Gateway" + source_type: "ipam.ipaddress" + source_label: "Prefix Gateway" + type: "one-to-one" + destination_type: "ipam.prefix" + destination_label: "Is the Gateway for the Prefix:" + source_hidden: false + destination_hidden: false + - name: "Vlan Group to Devices" + source_type: "ipam.vlangroup" + source_label: "Device List" + type: "one-to-many" + destination_type: "dcim.device" + destination_label: "VLAN Group" + source_hidden: false + destination_hidden: false diff --git a/ansible/roles/relationships/tasks/main.yml b/ansible/roles/relationships/tasks/main.yml new file mode 100644 index 000000000..07d3cfd66 --- /dev/null +++ b/ansible/roles/relationships/tasks/main.yml @@ -0,0 +1,22 @@ +--- + +- name: "Create Relationships" + ansible.builtin.uri: + url: "{{ nautobot_url }}/api/extras/relationships/" + method: POST + headers: + Authorization: "Token {{ nautobot_token }}" + body_format: json + body: + label: "{{ item.name }}" + source_type: "{{ item.source_type }}" + source_label: "{{ item.source_label }}" + source_hidden: "{{ item.source_hidden }}" + type: "{{ item.type }}" + destination_type: "{{ item.destination_type }}" + destination_label: "{{ item.destination_label }}" + destination_hidden: "{{ item.destination_hidden }}" + status_code: + - 201 + - 400 + loop: "{{ relationships_defaults }}" diff --git a/ansible/roles/roles/tasks/main.yml b/ansible/roles/roles/tasks/main.yml new file mode 100644 index 000000000..adce7bef2 --- /dev/null +++ b/ansible/roles/roles/tasks/main.yml @@ -0,0 +1,37 @@ +--- + +- name: Create Default Roles + networktocode.nautobot.role: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: "{{ item.name }}" + content_types: "{{ item.types }}" + color: "{{ item.color }}" + state: present + loop: + - {"name": "server", "types": ["dcim.device"], "color": "9e9e9e"} + - {"name": "Access", "types": ["dcim.device"], "color": "9e9e9e"} + - {"name": "Aggr", "types": ["dcim.device"], "color": "9e9e9e"} + - {"name": "Console", "types": ["dcim.device"], "color": "9e9e9e"} + - {"name": "Core", "types": ["dcim.device"], "color": "9e9e9e"} + - {"name": "Distro", "types": ["dcim.device"], "color": "9e9e9e"} + - {"name": "edge", "types": ["dcim.device"], "color": "9e9e9e"} + - {"name": "firewall", "types": ["dcim.device"], "color": "9e9e9e"} + - {"name": "load_balancer", "types": ["dcim.device"], "color": "9e9e9e"} + - {"name": "routeserver", "types": ["dcim.device"], "color": "9e9e9e"} + - {"name": "security", "types": ["dcim.device"], "color": "9e9e9e"} + - {"name": "Tenant leaf", "types": ["dcim.device"], "color": "2196f3"} + - {"name": "Edge leaf", "types": ["dcim.device"], "color": "9e9e9e"} + - {"name": "AggExNet", "types": ["ipam.prefix"], "color": "9e9e9e"} + - {"name": "Server BMC", "types": ["ipam.prefix"], "color": "9e9e9e"} + - {"name": "BMC Switch", "types": ["dcim.device"], "color": "ffc107"} + - {"name": "Public", "types": ["ipam.prefix"], "color": "9e9e9e"} + - {"name": "Spine", "types": ["dcim.device"], "color": "cddc39"} + - {"name": "Undercloud Infrastructure", "types": ["dcim.device"], "color": "aa1409"} + - {"name": "NetDev", "types": ["ipam.prefix"], "color": "9e9e9e"} + - {"name": "ServiceNet", "types": ["ipam.prefix"], "color": "9e9e9e"} + - {"name": "Network Infra", "types": ["ipam.prefix"], "color": "9e9e9e"} + - {"name": "svi_provisioning", "types": ["dcim.interface", "vni_custom_model.ucvni"], "color": "a30000"} + - {"name": "svi_vxlan_anycast_gateway", "types": ["dcim.interface", "vni_custom_model.ucvni"], "color": "d9381e"} + - {"name": storage-client-leaf", "types": ["dcim.device"], "color": "00ffff"} + - {"name": storage-target-leaf", "types": ["dcim.device"], "color": "ff9800"} diff --git a/ansible/roles/secrets/README.md b/ansible/roles/secrets/README.md new file mode 100644 index 000000000..99c4b7f12 --- /dev/null +++ b/ansible/roles/secrets/README.md @@ -0,0 +1,53 @@ +# Adding Secrets + +In your variables you'll need to define something like: + +```yaml +secrets_nb_secrets_groups: + gh_dev_type_pat: + - access_type: "HTTP(S)" + secret_type: token + name: test_pw + provider: pwsafe + parameters: + project: "37639" + cred: "329557" + field: password + tacacs_user_a_http: + - access_type: "HTTP(S)" + secret_type: "username" + name: tacacs_user + provider: pwsafe + parameters: + project: "38518" + cred: "336939" + field: username + - access_type: "HTTP(S)" + secret_type: "password" + name: tacacs_pass + provider: pwsafe + parameters: + project: "38518" + cred: "336939" + field: password + tacacs_user_b_http: + - access_type: "HTTP(S)" + secret_type: "username" + name: tacacs_user + provider: pwsafe + parameters: + project: "38518" + cred: "336940" + field: username + - access_type: "HTTP(S)" + secret_type: "password" + name: tacacs_pass + provider: pwsafe + parameters: + project: "38518" + cred: "336940" + field: password +``` + +Where the `key` is the unique reference which can be used in other places +like Git Repos. diff --git a/ansible/roles/secrets/defaults/main.yaml b/ansible/roles/secrets/defaults/main.yaml new file mode 100644 index 000000000..bb66ef5af --- /dev/null +++ b/ansible/roles/secrets/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +secrets_nb_secrets_groups: {} diff --git a/ansible/roles/secrets/tasks/main.yml b/ansible/roles/secrets/tasks/main.yml new file mode 100644 index 000000000..6c03bbd4d --- /dev/null +++ b/ansible/roles/secrets/tasks/main.yml @@ -0,0 +1,5 @@ +--- + +- name: Define secrets + ansible.builtin.include_tasks: secret_group.yml + loop: "{{ secrets_nb_secrets_groups | dict2items }}" diff --git a/ansible/roles/secrets/tasks/secret.yml b/ansible/roles/secrets/tasks/secret.yml new file mode 100644 index 000000000..3773218a8 --- /dev/null +++ b/ansible/roles/secrets/tasks/secret.yml @@ -0,0 +1,10 @@ +--- + +- name: Create the Secret + networktocode.nautobot.secret: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: "{{ single_secret.name }}" + provider: "{{ single_secret.provider }}" + parameters: "{{ single_secret.parameters }}" + state: present diff --git a/ansible/roles/secrets/tasks/secret_group.yml b/ansible/roles/secrets/tasks/secret_group.yml new file mode 100644 index 000000000..b80a2816c --- /dev/null +++ b/ansible/roles/secrets/tasks/secret_group.yml @@ -0,0 +1,25 @@ +--- + +- name: Loop to create secret + ansible.builtin.include_tasks: secret.yml + loop: "{{ item.value }}" + loop_control: + loop_var: single_secret + +- name: Create the Secret Group + networktocode.nautobot.secrets_group: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: "{{ item.key }}" + +- name: Associate the secrets to the group + networktocode.nautobot.secrets_groups_association: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + secrets_group: "{{ item.key }}" + access_type: "{{ secrets_list.access_type }}" + secret_type: "{{ secrets_list.secret_type }}" + secret: "{{ secrets_list.name }}" + loop: "{{ item.value }}" + loop_control: + loop_var: secrets_list diff --git a/ansible/roles/statuses/defaults/main.yaml b/ansible/roles/statuses/defaults/main.yaml new file mode 100644 index 000000000..9000d69cf --- /dev/null +++ b/ansible/roles/statuses/defaults/main.yaml @@ -0,0 +1,104 @@ +--- +statuses_defaults: + - name: Active + description: Unit is active + color: 4caf50 + content_types: + - circuits.circuit + - dcim.device + - dcim.powerfeed + - dcim.rack + - ipam.ipaddress + - ipam.prefix + - ipam.vlan + - virtualization.virtualmachine + - virtualization.vminterface + - dcim.interface + - dcim.location + - dcim.deviceredundancygroup + - dcim.interfaceredundancygroup + - extras.contactassociation + - dcim.softwareimagefile + - dcim.softwareversion + - dcim.controller + - vni_custom_model.ucvni + + - name: Provisioning + description: A device that is actively being provisioned + color: 9e9e9e + content_types: + - circuits.circuit + - dcim.device + + - name: Inventory + description: A device that is idle and pending deployment to some customer solution + color: 9e9e9e + content_types: + - dcim.device + - dcim.controller + + - name: Planned + description: A device that is being populated into Nautobot but not yet active in Ironic (new cabinet, new hardware, etc) + content_types: + - circuits.circuit + - dcim.cable + - dcim.device + - dcim.powerfeed + - dcim.rack + - virtualization.virtualmachine + - virtualization.vminterface + - dcim.interface + - dcim.location + - dcim.deviceredundancygroup + - dcim.interfaceredundancygroup + - dcim.controller + + - name: ActivelyManaged + description: Device is online and should be managed by Undersync + content_types: + - dcim.device + + - name: Allocated / In-Use + description: IP Block is in use + content_types: + - ipam.prefix + color: ff5722 + + - name: Provisioning-Interface + description: State for interface to be in provisioning network + content_types: + - dcim.interface + color: ff5722 + + - name: Quarantine + description: Device is quarantined + content_types: + - dcim.device + + # https://rackspace.atlassian.net/browse/PUC-730 + - name: Available + description: Unit is available + color: 4caf50 + content_types: + - dcim.device + - dcim.rack + + # https://rackspace.atlassian.net/browse/PUC-730 + - name: Staged + description: Unit has been staged + color: 2196f3 + content_types: + - dcim.device + - virtualization.virtualmachine + - dcim.controller + - dcim.module + + # https://rackspace.atlassian.net/browse/PUC-730 + - name: Decom + description: Unit has been staged + color: 2196f3 + content_types: + - dcim.device + - virtualization.virtualmachine + - dcim.controller + - dcim.module diff --git a/ansible/roles/statuses/tasks/main.yaml b/ansible/roles/statuses/tasks/main.yaml new file mode 100644 index 000000000..194bae6f7 --- /dev/null +++ b/ansible/roles/statuses/tasks/main.yaml @@ -0,0 +1,12 @@ +--- + +- name: Create Understack specific Device Statuses + networktocode.nautobot.status: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: "{{ item.name }}" + description: "{{ item.description }}" + state: present + color: "{{ item.color | default('01bea3') }}" + content_types: "{{ item.content_types | default([]) }}" + loop: "{{ statuses_defaults }}" diff --git a/ansible/roles/tags/tasks/main.yaml b/ansible/roles/tags/tasks/main.yaml new file mode 100644 index 000000000..10f486beb --- /dev/null +++ b/ansible/roles/tags/tasks/main.yaml @@ -0,0 +1,18 @@ +--- + +- name: Create Tags + networktocode.nautobot.tag: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + name: "{{ item.name }}" + content_types: "{{ item.content_types }}" + color: "{{ item.color }}" + loop: + - name: "leaf a side" + content_types: + - "dcim.device" + color: "f44336" + - name: "leaf b side" + content_types: + - "dcim.device" + color: "3f51b5" diff --git a/components/nautobot/values.yaml b/components/nautobot/values.yaml index 8ede491dc..d94a66d67 100644 --- a/components/nautobot/values.yaml +++ b/components/nautobot/values.yaml @@ -95,3 +95,44 @@ metrics: enabled: true prometheusRule: enabled: true + +extraObjects: + - apiVersion: batch/v1 + kind: Job + metadata: + generateName: sync-nautobot-ansible- + namespace: nautobot + annotations: + "helm.sh/hook": post-install,post-upgrade + spec: + template: + spec: + containers: + - name: ansible-runner + image: ghcr.io/rackerlabs/understack/ansible:latest + imagePullPolicy: Always + command: ["ansible-runner", "run", "/runner", "--playbook", "nautobot-initial-setup.yaml"] + env: + - name: NAUTOBOT_TOKEN + valueFrom: + secretKeyRef: + name: nautobot-superuser + key: apitoken + - name: NAUTOBOT_URL + value: http://nautobot-default.nautobot.svc.cluster.local + volumeMounts: + - name: ansible-inventory + mountPath: /runner/inventory/ + - name: ansible-group-vars + mountPath: /runner/inventory/group_vars/ + restartPolicy: Never + volumes: + - name: runner-data + emptyDir: {} + - name: ansible-inventory + configMap: + name: ansible-inventory + - name: ansible-group-vars + configMap: + name: ansible-group-vars + backoffLimit: 1 diff --git a/docs/component-ansible.md b/docs/component-ansible.md index eb60424c6..55cbf6ac8 100644 --- a/docs/component-ansible.md +++ b/docs/component-ansible.md @@ -17,6 +17,10 @@ The execution environment within the container is [ansible-runner][ansible-runne An inventory directory is necessary to be provided which would be part of your system deployment data. +Anything which is rackspace specific will be mounted inside ansible container. + +For example ansible hosts file and group-vars which are [deployment specific](https://github.com/RSS-Engineering/undercloud-deploy/tree/main/bravo-uc-iad3-dev/inventory) are created as [config-maps](https://github.com/RSS-Engineering/undercloud-deploy/blob/main/bravo-uc-iad3-dev/manifests/nautobot/kustomization.yaml#L13-L23) and [mounted as volumes](https://github.com/rackerlabs/understack/blob/main/components/keystone/values.yaml#L156-L167) + ## Sample Execution ```bash diff --git a/python/understack-workflows/tests/json_samples/event-interface-update.json b/python/understack-workflows/tests/json_samples/event-interface-update.json index 9c3223870..f31ad24b6 100644 --- a/python/understack-workflows/tests/json_samples/event-interface-update.json +++ b/python/understack-workflows/tests/json_samples/event-interface-update.json @@ -88,7 +88,7 @@ "id": "3709c38e-1f4f-4cdf-a9d4-45ad14e9f3dd", "url": "/api/extras/statuses/3709c38e-1f4f-4cdf-a9d4-45ad14e9f3dd/", "name": "Active", - "color": "4calf50", + "color": "4caf50", "created": "2024-04-29T00:00:00Z", "display": "Active", "notes_url": "/api/extras/statuses/3709c38e-1f4f-4cdf-a9d4-45ad14e9f3dd/notes/", @@ -247,7 +247,7 @@ "id": "3709c38e-1f4f-4cdf-a9d4-45ad14e9f3dd", "url": "/api/extras/statuses/3709c38e-1f4f-4cdf-a9d4-45ad14e9f3dd/", "name": "Active", - "color": "4calf50", + "color": "4caf50", "created": "2024-04-29T00:00:00Z", "display": "Active", "notes_url": "/api/extras/statuses/3709c38e-1f4f-4cdf-a9d4-45ad14e9f3dd/notes/", @@ -406,7 +406,7 @@ "id": "3709c38e-1f4f-4cdf-a9d4-45ad14e9f3dd", "url": "/api/extras/statuses/3709c38e-1f4f-4cdf-a9d4-45ad14e9f3dd/", "name": "Active", - "color": "4calf50", + "color": "4caf50", "created": "2024-04-29T00:00:00Z", "display": "Active", "notes_url": "/api/extras/statuses/3709c38e-1f4f-4cdf-a9d4-45ad14e9f3dd/notes/",