diff --git a/roles/redis/README.md b/roles/redis/README.md new file mode 100644 index 0000000..cff9699 --- /dev/null +++ b/roles/redis/README.md @@ -0,0 +1,47 @@ +# `finallycoffee.databases.redis` ansible role + +Redis is the self-proclaimed world's fastest data platform for caching, +vector search and NoSQL databases. Since version 7.2.4, it is no longer +considered "Free and open source software" (FOSS), with redis switching +their license to the "Serverside public license" (SSPL). + +Setting the `redis_version` to higher than `7.2.4` means you will deploy +the SSPL-licensed version to redis. + +## Configuration + +All container-related options to the `docker_container` ansible module +are available under the `redis_container_*` namespace, for example use +`redis_container_ports: [ '127.0.0.1:6379:6370/tcp' ]` to map the +containers port 6379 to the docker host. + +Redis-related config options are either available in the `redis_config_*` +namespace or can be specified by setting them as a dictionary in +`redis_config` + +### Authentication and authorization + +Redis ACL can be specified as an array in the `redis_config_user` variable + - see [the redis documentation](https://github.com/redis/redis/blob/unstable/redis.conf#L869) +for the format. Per default, the `default` user is able to connect without +any password. To require a password and use a different user, override +the variable, for example `redis_config_user: [ 'username on +@all -DEBUG ~* >secret' ]`. + +## Redis on a unix socket + +To make redis available on a unix socket, a directory must be supplied in which the +socket lives: +```yaml +redis_container_socket: /var/run/redis.sock +redis_container_volumes: + - "/path/to/socket/on/host/redis.sock:{{ redis_container_socket }}:z" +redis_config_unixsocket: "{{ redis_container_socket }}" +``` + +## Container specific information + +Redis publishes their official container image in both a debian-based and an +alpine-based variant. Which image should be used can be configured in +`redis_container_image_flavour`, which defaults to `alpine`, which is smaller +in size but also includes less related / debugging tools. To use the debian- +based image, unset the flavour using `redis_container_image_flavour: ~`. diff --git a/roles/redis/defaults/main/config.yml b/roles/redis/defaults/main/config.yml new file mode 100644 index 0000000..aabe8e5 --- /dev/null +++ b/roles/redis/defaults/main/config.yml @@ -0,0 +1,41 @@ +--- +redis_config_bind: + - "127.0.0.1" + - "-::1" +redis_config_protected_mode: true +redis_config_port: 6379 +redis_config_unixsocket: ~ +redis_config_unixsocketperm: '700' +redis_config_user: + - "default on +@all -DEBUG ~* nopass" +redis_config_databases: 16 +redis_config_supervised: false +redis_config_daemonize: false +redis_config_dbfilename: dump.rdb +redis_config_dir: "{{ redis_data_path }}" +redis_config_save: "3600 1 300 100 60 10000" +redis_config_appendfsync: everysec + +redis_base_config: + bind: "{{ redis_config_bind | join(' ') }}" + "protected-mode": "{{ redis_config_protected_mode | bool | ternary('yes', 'no') }}" + port: "{{ redis_config_port }}" + user: "{{ redis_config_user }}" + databases: "{{ redis_config_databases }}" + daemonize: "{{ redis_config_daemonize | bool | ternary('yes', 'no') }}" + supervised: "{{ redis_config_supervised | bool | ternary('yes', 'no') }}" + save: "{{ redis_config_save }}" + dbfilename: "{{ redis_config_dbfilename }}" + dir: "{{ redis_config_dir }}" + appendfsync: "{{ redis_config_appendfsync }}" + +redis_config: ~ +redis_config_merged: >-2 + {{ redis_config_base + | combine(({ + 'unixsocket': redis_config_unixsocket, + 'unixsocketperm': redis_config_unixsocketperm, + }) + if (redis_config_unixsocket | default(false, true)) else {}, + recursive=True) + | combine(redis_config | default({}, true), recursive=True) }} diff --git a/roles/redis/defaults/main/container.yml b/roles/redis/defaults/main/container.yml new file mode 100644 index 0000000..42f0fdc --- /dev/null +++ b/roles/redis/defaults/main/container.yml @@ -0,0 +1,48 @@ +--- +redis_container_image_registry: docker.io +redis_container_image_namespace: ~ +redis_container_image_name: redis +redis_container_image_tag: ~ +redis_container_image_flavour: alpine +redis_container_image_source: pull +redis_container_image_force_source: >-2 + {{ redis_container_image_tag | default(false, true) | bool }} +redis_container_image: >-2 + {{ + ([ + redis_container_image_registry | default([], true), + redis_container_image_namespace | default([], true), + redis_container_image_name, + ] | flatten | join('/')) + + ':' + + (redis_container_image_tag | default( + redis_version + ( + (redis_container_image_flavour | default(false, true) | bool) + | ternary('-' + (redis_container_image_flavour | default('')), '') + ), + true, + )) + }} + +redis_container_name: "redis{{ redis_instance_suffix }}" +redis_container_env: ~ +redis_container_user: >-2 + {{ redis_run_user_id }}:{{ redis_run_group_id }} +redis_container_ports: ~ +redis_container_labels: ~ +redis_container_volumes: ~ +redis_container_merged_volumes: >-2 + {{ redis_container_base_volumes + + redis_container_volumes | default([], true) }} +redis_container_command: + - "redis-server" + - "{{ redis_config_file }}" +redis_container_networks: ~ +redis_container_etc_hosts: ~ +redis_container_dns_servers: ~ +redis_container_restart_policy: "unless-stopped" +redis_container_state: >-2 + {{ (redis_state == 'present') | default('started', 'absent') }} +redis_container_base_volumes: + - "{{ redis_config_file }}:{{ redis_config_file }}:ro" + - "{{ redis_data_path }}:{{ redis_data_path }}:rw" diff --git a/roles/redis/defaults/main/main.yml b/roles/redis/defaults/main/main.yml new file mode 100644 index 0000000..c60586c --- /dev/null +++ b/roles/redis/defaults/main/main.yml @@ -0,0 +1,14 @@ +--- +redis_version: "7.2.4" +redis_instance: ~ +redis_instance_suffix: >-2 + {{ (redis_instance | default(false, true) | bool) + | ternary('-' + (redis_instance | default('')), '') }} +redis_user: >-2 + redis{{ redis_instance_suffix }} + +redis_config_path: "/etc/redis" +redis_config_file: >-2 + {{ redis_config_path }}/redis{{ redis_instance_suffix }}.conf +redis_data_path: "/var/lib/redis{{ redis_instance_suffix }}" +redis_deployment_method: docker diff --git a/roles/redis/defaults/main/user.yml b/roles/redis/defaults/main/user.yml new file mode 100644 index 0000000..47e7ef5 --- /dev/null +++ b/roles/redis/defaults/main/user.yml @@ -0,0 +1,10 @@ +--- +redis_run_user_id: >-2 + {{ redis_user_info.uid | default(redis_user, true) }} +redis_run_group_id: >-2 + {{ redis_user_info.group | default(redis_user, true) }} +redis_user_system: true +redis_user_create_home: false +redis_user_groups: ~ +redis_user_append_groups: >-2 + {{ redis_user_groups | default(true, false) | bool }} diff --git a/roles/redis/handlers/main.yml b/roles/redis/handlers/main.yml new file mode 100644 index 0000000..55c9198 --- /dev/null +++ b/roles/redis/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: Ensure redis container '{{ redis_container_name }}' is restarted + community.docker.docker_container: + name: "{{ redis_container_name }}" + state: "{{ redis_container_state }}" + restart: true + listen: redis-restart + when: + - deployment_method == 'docker' + - redis_state == 'present' diff --git a/roles/redis/meta/main.yml b/roles/redis/meta/main.yml new file mode 100644 index 0000000..b2e6798 --- /dev/null +++ b/roles/redis/meta/main.yml @@ -0,0 +1,10 @@ +--- +allow_duplicates: true +dependencies: [] +galaxy_info: + role_name: redis + description: >-2 + Deploy and configure redis, a fast caching, vector-search and NoSQL database. + galaxy_tags: + - redis + - docker diff --git a/roles/redis/tasks/deploy-docker.yml b/roles/redis/tasks/deploy-docker.yml new file mode 100644 index 0000000..e0319a4 --- /dev/null +++ b/roles/redis/tasks/deploy-docker.yml @@ -0,0 +1,26 @@ +--- +- name: Ensure container image '{{ redis_container_image }}' is {{ redis_state }} + community.docker.docker_image: + name: "{{ redis_container_image }}" + state: "{{ redis_state }}" + source: "{{ redis_container_image_source }}" + force_source: "{{ redis_container_image_force_source }}" + register: redis_container_image_info + until: redis_container_image_info is success + retries: 5 + delay: 3 + +- name: Ensure container '{{ redis_container_name }}' is {{ redis_container_state }} + community.docker.docker_container: + name: "{{ redis_container_name }}" + image: "{{ redis_container_image }}" + env: "{{ redis_container_env | default(omit, true) }}" + user: "{{ redis_container_user }}" + ports: "{{ redis_container_ports | default(omit, true) }}" + labels: "{{ redis_container_labels | default(omit, true) }}" + command: "{{ redis_container_command }}" + volumes: "{{ redis_container_merged_volumes }}" + networks: "{{ redis_container_networks | default(omit, true) }}" + etc_hosts: "{{ redis_container_etc_hosts | default(omit, true) }}" + dns_servers: "{{ redis_container_dns_servers | default(omit, true) }}" + state: "{{ redis_container_state }}" diff --git a/roles/redis/tasks/main.yml b/roles/redis/tasks/main.yml new file mode 100644 index 0000000..7cca1a7 --- /dev/null +++ b/roles/redis/tasks/main.yml @@ -0,0 +1,68 @@ +--- +- name: Ensure state is valid + ansible.builtin.fail: + msg: >-2 + Unsupported state '{{ redis_state }}'. + Supported states are {{ redis_states | join(', ') }} + when: redis_state not in redis_states + +- name: Ensure deployment method is valid + ansible.builtin.fail: + msg: >-2 + Unsupported deployment method '{{ redis_deployment_method }}'! + Supported methods are {{ redis_deployment_method | join(', ') }} + when: redis_deployment_method not in redis_deployment_methods + +- name: Ensure redis user '{{ redis_user }}' is {{ redis_state }} + ansible.builtin.user: + name: "{{ redis_user }}" + state: "{{ redis_state }}" + system: "{{ redis_user_system }}" + create_home: "{{ redis_user_create_home }}" + groups: "{{ redis_user_groups | default(omit, true) }}" + append: "{{ redis_user_append_groups | default(omit, true) }}" + register: redis_user_info + +- name: Ensure redis config file '{{ redis_config_file }}' is {{ redis_state }} + ansible.builtin.file: + path: "{{ redis_config_file }}" + state: "{{ redis_state }}" + when: redis_state == 'absent' + +- name: Ensure redis host directories are {{ redis_state }} + ansible.builtin.file: + path: "{{ path.name }}" + state: >-2 + {{ (redis_state == 'present') | ternary('directory', 'absent') }} + owner: "{{ path.owner | default(redis_run_user_id) }}" + group: "{{ path.group | default(redis_run_group_id) }}" + mode: "{{ path.mode | default('0755') }}" + loop: + - name: "{{ redis_config_path }}" + - name: "{{ redis_data_path }}" + loop_control: + loop_var: "path" + label: "{{ path.name }}" + +- name: Ensure redis config file '{{ redis_config_file }}' is {{ redis_state }} + ansible.builtin.copy: + content: |+2 + {% for tuple in (redis_merged_config | dict2items) %} + {%- if tuple.value is string -%} + {{ tuple.key }} {{ tuple.value }} + {%- elsif -%} + {% for value in tuple.value %} + {{ tuple.key }} {{ value }} + {% endfor %} + {%- endif -%} + {% endfor %} + dest: "{{ redis_config_file }}" + owner: "{{ redis_run_user_id }}" + group: "{{ redis_run_group_id }}" + mode: "0640" + when: redis_state == 'present' + notify: redis-restart + +- name: Deploy redis using {{ redis_deployment_method }} + ansible.builtin.include_tasks: + file: "deploy-{{ redis_deployment_method }}.yml" diff --git a/roles/redis/vars/main.yml b/roles/redis/vars/main.yml new file mode 100644 index 0000000..ee16e40 --- /dev/null +++ b/roles/redis/vars/main.yml @@ -0,0 +1,6 @@ +--- +redis_states: + - present + - absent +redis_deployment_methods: + - docker