From 403177a77a30131bb2e35d259c11aaa995155a6e Mon Sep 17 00:00:00 2001 From: Evelyn Alicke Date: Tue, 19 Mar 2024 18:28:35 +0100 Subject: [PATCH] feat(netbox): initial role --- playbooks/netbox.yml | 34 +++++++ roles/netbox/README.md | 3 + roles/netbox/defaults/main.yml | 136 +++++++++++++++++++++++++++ roles/netbox/files/loader.py | 12 +++ roles/netbox/handlers/main.yml | 28 ++++++ roles/netbox/tasks/main.yml | 114 ++++++++++++++++++++++ roles/netbox/templates/nginx.conf.j2 | 13 +++ roles/netbox/templates/uwsgi.ini.j2 | 5 + roles/netbox/vars/main.yml | 63 +++++++++++++ 9 files changed, 408 insertions(+) create mode 100644 playbooks/netbox.yml create mode 100644 roles/netbox/README.md create mode 100644 roles/netbox/defaults/main.yml create mode 100644 roles/netbox/files/loader.py create mode 100644 roles/netbox/handlers/main.yml create mode 100644 roles/netbox/tasks/main.yml create mode 100644 roles/netbox/templates/nginx.conf.j2 create mode 100644 roles/netbox/templates/uwsgi.ini.j2 create mode 100644 roles/netbox/vars/main.yml diff --git a/playbooks/netbox.yml b/playbooks/netbox.yml new file mode 100644 index 0000000..b134a40 --- /dev/null +++ b/playbooks/netbox.yml @@ -0,0 +1,34 @@ +--- +- hosts: [netbox] + become: true + strategy: free + roles: + - role: famedly.base.postgresql_client_access + vars: + postgresql_client_access_users: "{{ netbox_postgresql_users }}" + postgresql_client_access_databases: "{{ netbox_postgresql_databases }}" + postgresql_client_access_hba_entries: "{{ netbox_postgresql_hba_entries }}" + - role: famedly.base.redis + become: true + vars: + redis_prefix: "netbox_" + redis_user: "{{ netbox_redis_username }}" + redis_secret: "{{ netbox_redis_password }}" + redis_docker_ports: + - "{{ famedly_docker_gateway_ip }}:6379:6379" + - role: famedly.services.netbox + + vars: + routing_service_name: netbox + routing_server_port: "{{ netbox_container_port }}" + routing_traffic_public_rule: >- + HostRegexp(`{{ netbox_domain }}`, `{subdomain:.+}.{{ netbox_domain }}`) + && !Path(`/debug/metrics`) + routing_traffic_metrics_rule: Path(`/{{ routing_service_name }}/metrics`) + routing_traffic_metrics_middlewares: + - replacepath + famedly_traefik_middlewares: + replacepath: + replacepath: + path: "/debug/metrics" + netbox_container_labels: "{{ famedly_traefik_labels_flat }}" diff --git a/roles/netbox/README.md b/roles/netbox/README.md new file mode 100644 index 0000000..6166ea9 --- /dev/null +++ b/roles/netbox/README.md @@ -0,0 +1,3 @@ +# famedly.services.netbox +A role for deploying netbox, in a somewhat opinionated way. + diff --git a/roles/netbox/defaults/main.yml b/roles/netbox/defaults/main.yml new file mode 100644 index 0000000..dd3393c --- /dev/null +++ b/roles/netbox/defaults/main.yml @@ -0,0 +1,136 @@ +--- +netbox_path_base: /opt/netbox + +netbox_version: "3.7.5" + +netbox_user_name: netbox + +netbox_fqdn: "https://{{ netbox_domain }}" +netbox_allowed_hosts: + - "{{ netbox_domain }}" + +netbox_login_required: true + +# Extra Plugins to install +netbox_plugins: + - 'netbox_topology_views' + - 'netbox_inventory' + - 'netbox_bgp' + +# Quality of Life +netbox_protection_rules: + "dcim.site": + - "status": + - "eq": "decommisioning" + +netbox_path_yaml_config: "{{ netbox_path_base }}/config" +netbox_path_state: "{{ netbox_path_base }}/state" +netbox_path_static: "{{ netbox_path_base }}/netbox/static" +netbox_path_socks: "{{ netbox_path_base }}/socks" +netbox_path_socks_uwsgi: "{{ netbox_path_socks }}/uwsgi.sock" +netbox_path_nginx_config: "{{ netbox_path_base }}/nginx.conf" +netbox_path_uwsgi_config: "{{ netbox_path_base }}/uwsgi.ini" +netbox_file_config_py: "{{ netbox_path_base }}/netbox/netbox/configuration.py" + +netbox_container_port: 8000 + +netbox_container_name: "netbox" +netbox_container_env: {} +netbox_container_ports: [] +netbox_container_labels_merged: >- + {{ netbox_container_labels_base | + combine(netbox_container_labels | default({})) }} +netbox_container_labels_base: + version: "{{ netbox_version }}" +netbox_container_volumes_common: + - "{{ netbox_path_base ~ '/configuration.py' ~ ':' ~ netbox_file_config_py }}" + - "{{ netbox_path_yaml_config ~ ':' ~ netbox_path_yaml_config }}" + - "{{ netbox_path_state ~ ':' ~ netbox_path_state }}" + - "{{ netbox_postgres_socket ~ ':' ~ netbox_postgres_socket }}" + - "{{ netbox_path_static ~ ':' ~ netbox_path_static }}" + - "{{ netbox_path_socks ~ ':' ~ netbox_path_socks }}" + - "{{ netbox_path_uwsgi_config ~ ':' ~ netbox_path_uwsgi_config }}" + - "/etc/passwd:/etc/passwd:ro" + +netbox_container_volumes_nginx: + - "{{ netbox_path_nginx_config }}:/etc/nginx/conf.d/netbox.conf" + - "{{ netbox_path_state ~ ':' ~ netbox_path_state }}" + - "{{ netbox_path_static ~ ':' ~ netbox_path_static }}" + - "{{ netbox_path_socks ~ ':' ~ netbox_path_socks }}" + +netbox_container_restart_policy: "always" +netbox_container_image_state: "present" +netbox_container_image_force_pull: true +netbox_container_image_reference: >- + {{ + netbox_container_image_repository + ~ ":" + ~ netbox_container_image_tag | default('v' ~ netbox_version ) + }} +netbox_container_image_repository: >- + {{ + ( + container_registries[netbox_container_image_registry] + | default(netbox_container_image_registry) + ) + ~ '/' + ~ netbox_container_image_namespace | default('') + ~ netbox_container_image_name + }} + +netbox_container_nginx_image_reference: >- + {{ + netbox_container_nginx_image_repository + ~ ":" + ~ netbox_container_nginx_image_tag | default('bookworm') + }} +netbox_container_nginx_image_repository: >- + {{ + ( + container_registries[netbox_container_nginx_image_registry] + | default(netbox_container_nginx_image_registry) + ) + ~ '/' + ~ netbox_container_nginx_image_namespace | default('') + ~ netbox_container_nginx_image_name + }} + +netbox_container_image_registry: "docker-oss.nexus.famedly.de" +netbox_container_image_name: "netbox" + +netbox_container_nginx_image_registry: "docker.io" +netbox_container_nginx_image_name: "nginx" + +netbox_postgres_name: "netbox" +netbox_postgres_user: "netbox" +# netbox_postgres_password: ~ +netbox_postgres_host: "{{ netbox_postgres_socket }}" + +netbox_redis_host: ~ +netbox_redis_port: 6379 +netbox_redis_username: "netbox" +# netbox_redis_password: ~ +netbox_redis_tasks_db: 0 +netbox_redis_cache_db: 1 +netbox_redis_ssl: false + +# netbox_secret_key: ~ + +netbox_allow_token_retrival: false +netbox_secure_cookies: true + +# netbox_oidc_enable: false +# netbox_oidc_admin_group: ~ +# netbox_oidc_staff_group: ~ +# netbox_oidc_endpoint: ~ +# netbox_oidc_key: ~ +# netbox_oidc_secret: ~ + +netbox_metrics_enabled: true + +netbox_nginx_listen: 8000 + +netbox_container_commands: + - { name: "uwsgi", entry: "/opt/netbox/venv/bin/uwsgi {{ netbox_path_uwsgi_config }}" } + - { name: "workers", entry: "/opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py rqworker high default low" } + - { name: "housekeeping", entry: "/opt/netbox/housekeeping.sh" } diff --git a/roles/netbox/files/loader.py b/roles/netbox/files/loader.py new file mode 100644 index 0000000..db450f0 --- /dev/null +++ b/roles/netbox/files/loader.py @@ -0,0 +1,12 @@ +import yaml +import os + +def load_dir_rec(path): + for node in os.scandir(path): + if node.is_dir(): + load_dir(node.path) + else: + for k,v in yaml.safe_load(open(node.path)).items(): + globals()[k] = v # we just add *all* the kv pairs to the scope netbox will use to access it's config + +load_dir_rec(os.environ.get('NETBOX_YAML_CONFIG_DIR','/opt/netbox/config')) diff --git a/roles/netbox/handlers/main.yml b/roles/netbox/handlers/main.yml new file mode 100644 index 0000000..4d91a61 --- /dev/null +++ b/roles/netbox/handlers/main.yml @@ -0,0 +1,28 @@ +--- +- name: Restart uwsgi + community.docker.docker_container: + name: "{{ netbox_container_name }}_uwsgi" + state: "started" + restart: true + listen: "restart-netbox-web" + +- name: Restart redis queue and task workers + community.docker.docker_container: + name: "{{ netbox_container_name }}_workers" + state: "started" + restart: true + listen: "restart-netbox-web" + +- name: Restart nginx + community.docker.docker_container: + name: "{{ netbox_container_name }}_nginx" + state: "started" + restart: true + listen: "restart-netbox-nginx" + +- name: Restart housekeeping ( and migrate database ) + community.docker.docker_container: + name: "{{ netbox_container_name }}_housekeeping" + state: "started" + restart: true + listen: "migrate-netbox" diff --git a/roles/netbox/tasks/main.yml b/roles/netbox/tasks/main.yml new file mode 100644 index 0000000..94a5a53 --- /dev/null +++ b/roles/netbox/tasks/main.yml @@ -0,0 +1,114 @@ +--- +- name: Add the user "{{ netbox_user_name }}" + ansible.builtin.user: + name: "{{ netbox_user_name }}" + groups: "{{ netbox_user_name }}" + create_home: false + register: netbox_user + become: true + +- name: "Create directories for netbox" + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: "0755" + owner: "{{ netbox_user.name }}" + group: "{{ netbox_user.group }}" + become: true + loop: + - "{{ netbox_path_base }}" + - "{{ netbox_path_yaml_config }}" + - "{{ netbox_path_state }}" + +- name: "Write netbox config loader" + ansible.builtin.copy: + src: "loader.py" + dest: "{{ netbox_path_base }}/configuration.py" + mode: "0775" + +- name: "Write netbox nginx config" + ansible.builtin.template: + src: "nginx.conf.j2" + dest: "{{ netbox_path_nginx_config }}" + mode: "0775" + notify: ["restart-netbox-nginx"] + +- name: "Write netbox uwsgi config" + ansible.builtin.template: + src: "uwsgi.ini.j2" + dest: "{{ netbox_path_uwsgi_config }}" + mode: "0775" + notify: ["restart-netbox-web"] + +- name: "Write netbox config" + ansible.builtin.copy: + content: "{{ item.content | ansible.builtin.to_nice_yaml }}" + dest: "{{ netbox_path_yaml_config ~ '/' ~ item.dest }}" + mode: "0666" + loop: + - { content: "{{ netbox_flat_config }}", dest: "flat.yaml" } + notify: ["restart-netbox-web"] + +- name: Ensure container image is present locally + community.docker.docker_image: + name: "{{ netbox_container_image_reference }}" + source: "pull" + state: "{{ netbox_container_image_state }}" + force_source: "{{ netbox_container_image_force_pull }}" + register: netbox_container_image_pulled + until: netbox_container_image_pulled is success + retries: 10 + delay: 5 + become: true + tags: ["deploy", "deploy-netbox"] + notify: + - "migrate-netbox" + - "restart-netbox-web" + - "restart-netbox-nginx" + +- name: Ensure container image nginx is present locally + community.docker.docker_image: + name: "{{ netbox_container_nginx_image_reference }}" + source: "pull" + state: "{{ netbox_container_image_state }}" + force_source: "{{ netbox_container_image_force_pull }}" + register: netbox_container_image_pulled + until: netbox_container_image_pulled is success + retries: 10 + delay: 5 + become: true + tags: ["deploy", "deploy-netbox"] + +- name: Ensure container is started + community.docker.docker_container: + image: "{{ netbox_container_image_reference }}" + name: "{{ netbox_container_name ~ '_' ~ item.name }}" + state: "started" + restart_policy: "{{ netbox_container_restart_policy | default(omit) }}" + user: "{{ netbox_user.uid ~ ':' ~ netbox_user.group }}" + volumes: "{{ netbox_container_volumes_common }}" + ports: "{{ netbox_container_ports }}" + env: "{{ netbox_container_env | default(omit) }}" + command: "{{ item.entry }}" + etc_hosts: "{{ netbox_container_etc_hosts | default(omit) }}" + networks: "{{ netbox_container_networks | default(omit) }}" + purge_networks: "{{ netbox_container_purge_networks | default(omit) }}" + become: true + tags: ["deploy", "deploy-netbox"] + loop: "{{ netbox_container_commands }}" + +- name: Ensure container nginx is started + community.docker.docker_container: + image: "{{ netbox_container_nginx_image_reference }}" + name: "{{ netbox_container_name }}_nginx" + state: "started" + restart_policy: "{{ netbox_container_restart_policy | default(omit) }}" + volumes: "{{ netbox_container_volumes_nginx }}" + ports: "{{ netbox_container_ports }}" + env: "{{ netbox_container_env | default(omit) }}" + labels: "{{ netbox_container_labels_merged }}" + etc_hosts: "{{ netbox_container_etc_hosts | default(omit) }}" + networks: "{{ netbox_container_networks | default(omit) }}" + purge_networks: "{{ netbox_container_purge_networks | default(omit) }}" + become: true + tags: ["deploy", "deploy-netbox"] diff --git a/roles/netbox/templates/nginx.conf.j2 b/roles/netbox/templates/nginx.conf.j2 new file mode 100644 index 0000000..d05327d --- /dev/null +++ b/roles/netbox/templates/nginx.conf.j2 @@ -0,0 +1,13 @@ +server { + listen {{ netbox_nginx_listen }}; + server_name _; + + location /static/ { + alias {{ netbox_path_static }}/; + } + + location / { + include uwsgi_params; + uwsgi_pass unix://{{ netbox_path_socks_uwsgi }}; + } +} diff --git a/roles/netbox/templates/uwsgi.ini.j2 b/roles/netbox/templates/uwsgi.ini.j2 new file mode 100644 index 0000000..702e4c1 --- /dev/null +++ b/roles/netbox/templates/uwsgi.ini.j2 @@ -0,0 +1,5 @@ +[uwsgi] +chmod-socket = 777 +socket = {{ netbox_path_socks_uwsgi }} +chdir = /opt/netbox/netbox +wsgi-file = /opt/netbox/netbox/netbox/wsgi.py diff --git a/roles/netbox/vars/main.yml b/roles/netbox/vars/main.yml new file mode 100644 index 0000000..cec872f --- /dev/null +++ b/roles/netbox/vars/main.yml @@ -0,0 +1,63 @@ +--- + +netbox_flat_config: + ALLOWED_HOSTS: "{{ netbox_allowed_hosts }}" + + DATABASE: + ENGINE: "django.db.backends.postgresql" + NAME: "{{ netbox_postgres_name }}" + USER: "{{ netbox_postgres_user }}" + PASSWORD: "{{ netbox_postgres_password }}" + HOST: "{{ netbox_postgres_host }}" + + REDIS: + "tasks": + HOST: "{{ netbox_redis_host }}" + PORT: "{{ netbox_redis_port }}" + USERNAME: "{{ netbox_redis_username }}" + PASSWORD: "{{ netbox_redis_password }}" + DATABASE: "{{ netbox_redis_tasks_db }}" + SSL: "{{ netbox_redis_ssl }}" + "caching": + HOST: "{{ netbox_redis_host }}" + PORT: "{{ netbox_redis_port }}" + USERNAME: "{{ netbox_redis_username }}" + PASSWORD: "{{ netbox_redis_password }}" + DATABASE: "{{ netbox_redis_cache_db }}" + SSL: "{{ netbox_redis_ssl }}" + + SECRET_KEY: "{{ netbox_secret_key }}" + + ALLOW_TOKEN_RETRIEVAL: "{{ netbox_allow_token_retrival }}" + CSRF_COOKIE_SECURE: "{{ netbox_secure_cookies }}" + SESSION_COOKIE_SECURE: "{{ netbox_secure_cookies }}" + + LOGIN_REQUIRED: true + + # REMOTE_AUTH_ENABLED: "{{ netbox_oidc_enable }}" + # REMOTE_AUTH_BACKEND: "social_core.backends.oidc.OpenIdConnectAuth" + # REMOTE_AUTH_AUTO_CREATE_GROUPS: true + # REMOTE_AUTH_AUTO_CREATE_USER: true + # REMOTE_AUTH_GROUP_SYNC_ENABLED: true + # REMOTE_AUTH_SUPERUSER_GROUPS: + # - "{{ netbox_oidc_admin_group }}" + # REMOTE_AUTH_STAFF_GROUPS: + # - "{{ netbox_oidc_staff_group }}" + + # SOCIAL_AUTH_OIDC_OIDC_ENDPOINT: "{{ netbox_oidc_endpoint}}" + # SOCIAL_AUTH_OIDC_KEY: "{{ netbox_oidc_key}}" + # SOCIAL_AUTH_OIDC_SECRET: "{{ netbox_oidc_secret}}" + # SOCIAL_AUTH_OIDC_SCOPE: [ groups ] + + PROTECTION_RULES: "{{ netbox_protection_rules }}" + + POWERFEED_DEFAULT_VOLTAGE: 230 + + CENSUS_REPORTING_ENABLED: false + CHANGELOG_RETENTION: 500 + + CHANGELOG_SKIP_EMPTY_CHANGES: true + + METRICS_ENABLED: "{{ netbox_metrics_enabled | default(true) }}" + + PLUGINS: "{{ netbox_plugins }}"