User Guide#

Installation#

Install the collection from Ansible Galaxy:

ansible-galaxy collection install exasol.exasol

Collection modules also require the Python runtime package exasol-ansible-modules. Install it separately in the Python environment that executes the modules:

python -m pip install exasol-ansible-modules

For a pinned setup, use the version that matches the installed collection.

Security And Secret Handling#

Vault-Backed Secrets#

uman~document-vault-backed-secret-handling~1

Store Exasol credentials and other secret values in Ansible Vault or an equivalent external secret manager. Pass secrets into module parameters such as login_password from that protected source instead of writing them directly in playbooks, inventory, CI variables that are printed in logs, or reusable examples.

Use the same pattern for every automation environment that runs the collection: the playbook should receive the current secret value at runtime, and logs or shared artifacts should contain only variable names or redacted module output.

exasol_query is an explicit exception for SQL text: its executed_queries result returns the SQL supplied to the module. Never embed passwords, tokens, or other secrets in query text. Use positional_args or named_args for values that must be bound to a single statement, and source those values from Vault or another protected secret manager.

Secret Rotation And Revocation#

uman~keep-secret-rotation-and-revocation-outside-the-collection~1

Rotate and revoke credentials in their source systems: update the external secret store and the corresponding Exasol account state, then run playbooks with the updated secret values. The collection does not retain credentials across tasks and does not provide a credential-rotation workflow of its own.

When a credential is revoked or replaced, remove or update the old value in the secret source used by Ansible. Subsequent collection tasks should receive the new value through normal playbook variable resolution.

Least-Privilege Service Accounts#

uman~require-least-privilege-service-accounts-for-automation-tiers~1

Use separate Exasol service accounts for separate automation roles, and grant each account only the privileges needed by the playbooks it runs. For example, an account used for metadata checks does not need the same privileges as an account that manages users, roles, schemas, grants, or trusted direct SQL through exasol_query.

Keep these privilege boundaries in Exasol account provisioning and in the secret store that supplies the credentials. The collection executes with the permissions of the authenticated account and cannot downgrade an account that has already been granted broader privileges than the playbook requires.

Status: draft

Basic Playbook#

Module Names And FQCNs#

uman~explain-fqcn-module-naming~1

Ansible examples in this guide usually use fully qualified collection names (FQCNs), such as exasol.exasol.exasol_query. The three parts are:

  • exasol: the Ansible Galaxy namespace

  • exasol: the collection name

  • exasol_query: the module name

The apparent repetition in names such as exasol.exasol.exasol_query is therefore expected Ansible convention. It appears because the namespace, collection, and module names all share the Exasol project prefix.

Use FQCNs in reusable playbooks, shared snippets, role tasks, and documentation examples because they are unambiguous without relying on surrounding playbook context. Short module names are also valid when the playbook declares the collection:

---
- hosts: localhost
  gather_facts: false
  collections:
    - exasol.exasol
  tasks:
    - name: Read Exasol version metadata
      exasol_query:
        login_host: db.example.com
        login_user: "{{ vault_exasol_user }}"
        login_password: "{{ vault_exasol_password }}"
        query: SELECT PARAM_VALUE FROM SYS.EXA_METADATA

Declare the collection in a playbook and call modules with Exasol connection parameters:

---
- hosts: localhost
  gather_facts: false
  collections:
    - exasol.exasol
  tasks:
    - name: Read Exasol version metadata
      exasol.exasol.exasol_query:
        login_host: db.example.com
        login_user: "{{ vault_exasol_user }}"
        login_password: "{{ vault_exasol_password }}"
        login_schema: reporting
        query: >-
          SELECT PARAM_VALUE
          FROM SYS.EXA_METADATA
        WHERE PARAM_NAME = 'databaseProductVersion'

Use login_schema to select the schema opened by the connection. The former login_db name remains a deprecated alias for existing playbooks. If both are set, login_db takes precedence for backward compatibility; use only login_schema in new playbooks.

exasol_query#

uman~document-direct-sql-secret-exposure~1

Use exasol.exasol.exasol_query to execute SQL statements directly from an Ansible playbook. query can be a single SQL string or a list of SQL statements. A list runs on one Exasol connection in the supplied order.

Read-only statements report changed=false. DDL and DML statements report changed=true. The result contains rows from the last statement in query_result, one result list per statement in query_all_results, and per-statement rowcount and execution_time_ms values.

Bound arguments are available for single-statement queries only. Use positional_args for ? placeholders and named_args for :name placeholders. Statement batches do not accept bound arguments; split the batch into separate tasks if each statement needs its own bindings.

Validation Behavior#

exasol_query fails validation for argument-binding cases that would be ambiguous or unsafe to execute:

  • positional_args or named_args with a statement batch. The module does not infer which arguments belong to which statement.

  • A different number of ? placeholders and positional_args values.

  • Missing named_args values for :name placeholders, or extra named_args entries that are not used by the statement.

For statement batches that need bindings, split the batch into separate exasol_query tasks so each task has one SQL statement and one explicit argument set.

- name: Bind positional and named values in a single statement
  exasol.exasol.exasol_query:
    login_host: db.example.com
    login_user: "{{ vault_exasol_user }}"
    login_password: "{{ vault_exasol_password }}"
    query: SELECT ? AS A, :name AS B
    positional_args:
      - 42
    named_args:
      name: example

Run statement batches without bound arguments:

- name: Run a batch on one Exasol connection without bound arguments
  exasol.exasol.exasol_query:
    login_host: db.example.com
    login_user: "{{ vault_exasol_user }}"
    login_password: "{{ vault_exasol_password }}"
    query:
      - CREATE SCHEMA IF NOT EXISTS demo
      - CREATE OR REPLACE TABLE demo.t (id DECIMAL(18,0))
      - INSERT INTO demo.t VALUES (1)
      - SELECT COUNT(*) AS row_count FROM demo.t

In check mode, read-only statements are executed. If any statement in a batch is writable, the whole batch is skipped and the module reports the predicted change. This avoids partially executing a mixed read/write batch.

Because executed_queries returns the supplied SQL, do not put secret values in query text. Use the bound arguments described above for a single statement.

- name: Preview a mixed read-write batch in check mode
  exasol.exasol.exasol_query:
    login_host: db.example.com
    login_user: "{{ vault_exasol_user }}"
    login_password: "{{ vault_exasol_password }}"
    query:
      - SELECT COUNT(*) AS SCHEMA_COUNT FROM SYS.EXA_SCHEMAS
      - CREATE SCHEMA demo_check_mode
  check_mode: true

exasol_script#

Use exasol.exasol.exasol_script to execute a multi-statement SQL script directly from an Ansible playbook. Unlike exasol_query, script is always a single string, and the module relies on pyexasol’s execute_sql_script capability to split it into statements. Semicolons inside string literals, quoted identifiers, comments, and Exasol script bodies do not terminate statements; a script body such as a CREATE ... SCRIPT definition is terminated by a standalone / line instead.

- name: Run a batch of statements as one script
  exasol.exasol.exasol_script:
    login_host: db.example.com
    login_user: "{{ vault_exasol_user }}"
    login_password: "{{ vault_exasol_password }}"
    script: |
      CREATE SCHEMA IF NOT EXISTS demo;
      CREATE OR REPLACE TABLE demo.t (id DECIMAL(18,0));
      INSERT INTO demo.t VALUES (1);

- name: Create a script whose body contains semicolons
  exasol.exasol.exasol_script:
    login_host: db.example.com
    login_user: "{{ vault_exasol_user }}"
    login_password: "{{ vault_exasol_password }}"
    script: |
      CREATE SCRIPT demo.double_value(x) AS
      function run(ctx)
          local x = ctx.x; return x * 2
      end
      /

If a statement fails, pyexasol raises the original error and later statements do not run. exasol_script does not accept positional_args or named_args.

Check mode classifies the whole script as read-only or not, the same way exasol_query classifies a multi-statement batch. A script made up only of read-only statements executes normally and reports changed=false. Any other script is skipped, and the module reports changed=true with the whole supplied script as a single predicted entry in executed_queries, rather than the real per-statement breakdown that execution produces.

- name: Preview a script that would create a schema
  exasol.exasol.exasol_script:
    login_host: db.example.com
    login_user: "{{ vault_exasol_user }}"
    login_password: "{{ vault_exasol_password }}"
    script: |
      CREATE SCHEMA demo_check_mode;
  check_mode: true

exasol_info#

uman~document-public-module-workflows~1

Use exasol.exasol.exasol_info to gather basic Exasol server information from read-only metadata queries. The module returns the Exasol version, database name, and cluster size, and it always reports changed=false.

The module uses the same connection parameters as the other collection modules. Provide the Exasol login settings in the task as shown below:

---
- hosts: localhost
  gather_facts: false
  collections:
    - exasol.exasol
  tasks:
    - name: Gather Exasol server information
      exasol.exasol.exasol_info:
        login_host: db.example.com
        login_user: "{{ vault_exasol_user }}"
        login_password: "{{ vault_exasol_password }}"

Use check mode if you want to verify the module behavior in a dry run. The module still queries metadata and returns the same information:

- name: Gather Exasol server information in check mode
  exasol.exasol.exasol_info:
    login_host: db.example.com
    login_user: "{{ vault_exasol_user }}"
    login_password: "{{ vault_exasol_password }}"
  check_mode: true

exasol_grants#

Use exasol.exasol.exasol_grants to grant or revoke requested Exasol privileges for exactly one user or role. The module checks Exasol privilege metadata first, so repeated runs report changed=false when the requested grant state already matches the database.

state=present grants missing privileges. state=absent revokes only the privileges listed in the task; it does not remove unrelated grants from the principal.

Supported principals and grants:

  • exactly one principal, either user or role

  • direct system privileges through system_privileges

  • role memberships through roles

  • schema-level object privileges through object_privileges with schema and no object

  • schema-qualified object privileges through object_privileges with both schema and object

Supply at least one requested privilege. When an optional privilege list is not needed, omit the option instead of passing an empty list.

Set admin_option: true to grant system privileges or role memberships with WITH ADMIN OPTION. When admin_option is omitted or false, the module does not use WITH ADMIN OPTION. The option does not apply to object privileges. System privileges and role memberships can also set admin_option per item by using dictionary entries.

Supported system privileges:

ACCESS ANY CONNECTION, ALTER ANY CONNECTION, ALTER ANY SCHEMA, ALTER ANY TABLE, ALTER ANY VIRTUAL SCHEMA, ALTER ANY VIRTUAL SCHEMA REFRESH, ALTER SYSTEM, ALTER USER, CREATE ANY FUNCTION, CREATE ANY SCRIPT, CREATE ANY TABLE, CREATE ANY VIEW, CREATE CONNECTION, CREATE FUNCTION, CREATE ROLE, CREATE SCHEMA, CREATE SCRIPT, CREATE SESSION, CREATE TABLE, CREATE USER, CREATE VIEW, CREATE VIRTUAL SCHEMA, DELETE ANY TABLE, DROP ANY CONNECTION, DROP ANY FUNCTION, DROP ANY ROLE, DROP ANY SCHEMA, DROP ANY SCRIPT, DROP ANY TABLE, DROP ANY VIEW, DROP ANY VIRTUAL SCHEMA, DROP USER, EXECUTE ANY FUNCTION, EXECUTE ANY SCRIPT, EXPORT, GRANT ANY CONNECTION, GRANT ANY OBJECT PRIVILEGE, GRANT ANY PRIVILEGE, GRANT ANY ROLE, IMPERSONATE ANY USER, IMPORT, INSERT ANY TABLE, KILL ANY SESSION, MANAGE CONSUMER GROUPS, SELECT ANY DICTIONARY, SELECT ANY TABLE, SET ANY CONSUMER GROUP, UPDATE ANY TABLE, USE ANY CONNECTION, and USE ANY SCHEMA.

Supported object privileges:

ACCESS, ALTER, DELETE, EXECUTE, IMPERSONATION, INSERT, REFERENCES, REFRESH, SELECT, UPDATE, and USAGE.

Supported object types are function, script, table, view, and virtual_schema. The object_type option is optional; omit it for schema-level grants and ordinary table grants.

Mixing grant requests#

One exasol_grants task targets exactly one principal, but that task may contain more than one grant family. You can combine system_privileges, roles, and object_privileges in a single task when all requested grants belong to the same user or role and share the same state.

For example, one task can grant login rights, role membership, schema usage, and table access to app_user. The module checks each requested grant independently and only executes statements for missing grants.

Use separate tasks when:

  • different grants belong to different principals

  • some grants should be present and others should be absent

  • role membership should be managed separately from direct privileges for review or approval reasons

state=absent follows the same rule: it revokes only the requested grants from the selected principal. It does not drop users, drop roles, drop schemas, or remove unrelated grants from that principal.

When admin_option: false is requested for a system privilege or role membership that already has admin option, the module revokes and re-grants that specific grant without admin option. This keeps admin_option part of the desired state instead of silently accepting the existing stronger grant.

This version does not manage connection object grants, exclusive reconciliation, or broad ALL PRIVILEGES requests. User and role names are exact Exasol identifier values. Schema and object names use the collection’s conservative regular-identifier validation.

Grant a system privilege to a user:

- name: Grant CREATE SESSION to an application user
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    user: app_user
    system_privileges:
      - CREATE SESSION

Grant multiple system privileges to a user:

- name: Grant application user login and schema creation privileges
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    user: app_user
    system_privileges:
      - CREATE SESSION
      - CREATE SCHEMA
      - USE ANY SCHEMA

Grant a system privilege with admin option:

- name: Let a security role delegate SELECT ANY TABLE
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    role: security_admin
    system_privileges:
      - SELECT ANY TABLE
    admin_option: true

Grant mixed system privileges with per-privilege admin option:

- name: Grant system privileges with different delegation rights
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    role: security_admin
    system_privileges:
      - privilege: SELECT ANY TABLE
        admin_option: true
      - privilege: CREATE SESSION
        admin_option: false

The string form is a shorthand for a system privilege without admin option, unless task-level admin_option: true is set. A system privilege dictionary’s admin_option value overrides the task-level setting for that one privilege.

Grant role membership to a user:

- name: Grant the reader role to an application user
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    user: app_user
    roles:
      - app_reader

Grant role membership with admin option:

- name: Let an operator delegate a role membership
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    user: app_operator
    roles:
      - app_reader
    admin_option: true

Grant mixed role memberships with per-role admin option:

- name: Grant role memberships with different delegation rights
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    user: app_operator
    roles:
      - role: app_reader
        admin_option: true
      - role: app_writer
        admin_option: false

The string form is a shorthand for a role membership without admin option, unless task-level admin_option: true is set. A role dictionary’s admin_option value overrides the task-level setting for that one role.

Grant one role to another role:

- name: Grant reporting privileges through a role hierarchy
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    role: app_reporting
    roles:
      - app_reader

Grant a schema-scoped object privilege to a role:

- name: Grant schema usage to a reader role
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    role: app_reader
    object_privileges:
      - schema: app_schema
        privileges:
          - USAGE

Grant multiple object privileges to a role:

- name: Grant reader and writer privileges on application objects
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    role: app_writer
    object_privileges:
      - schema: app_schema
        privileges:
          - USAGE
      - schema: app_schema
        object: fact_sales
        privileges:
          - SELECT
          - INSERT
          - UPDATE
      - schema: app_schema
        object: sales_view
        object_type: view
        privileges:
          - SELECT

Grant system and object privileges in one task:

- name: Grant all requested privileges for an application service user
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    user: app_service
    system_privileges:
      - CREATE SESSION
    object_privileges:
      - schema: app_schema
        privileges:
          - USAGE
      - schema: app_schema
        object: fact_sales
        privileges:
          - SELECT
          - INSERT

Grant system privileges, role memberships, and object privileges in one task:

- name: Grant the full requested access bundle to a service user
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    user: app_service
    system_privileges:
      - CREATE SESSION
    roles:
      - app_reader
    object_privileges:
      - schema: app_schema
        privileges:
          - USAGE
      - schema: app_schema
        object: fact_sales
        object_type: table
        privileges:
          - SELECT
      - schema: app_schema
        object: sales_view
        object_type: view
        privileges:
          - SELECT

Revoke a requested object privilege without touching other grants:

- name: Revoke table SELECT from a user
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    user: app_user
    state: absent
    object_privileges:
      - schema: app_schema
        object: fact_sales
        privileges:
          - SELECT

Revoke multiple requested privileges from a role:

- name: Revoke write privileges while keeping unrelated grants intact
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    role: app_writer
    state: absent
    system_privileges:
      - CREATE SCHEMA
    object_privileges:
      - schema: app_schema
        object: fact_sales
        privileges:
          - INSERT
          - UPDATE

Revoke role membership and a direct privilege in one task:

- name: Remove temporary elevated access from a user
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    user: app_user
    state: absent
    system_privileges:
      - CREATE SCHEMA
    roles:
      - app_deployer

In check mode, exasol_grants still reads metadata but does not execute planned GRANT or REVOKE statements. The result’s executed_queries contains the statements that would run.

Preview a mixed grant task in check mode:

- name: Preview requested access changes
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    user: app_service
    system_privileges:
      - CREATE SESSION
    roles:
      - app_reader
    object_privileges:
      - schema: app_schema
        privileges:
          - USAGE
  check_mode: true

Keep different principals in separate tasks:

- name: Grant login to the application user
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    user: app_user
    system_privileges:
      - CREATE SESSION

- name: Grant schema access to the reader role
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    role: app_reader
    object_privileges:
      - schema: app_schema
        privileges:
          - USAGE

Keep grants and revokes in separate tasks:

- name: Grant read access
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    user: app_user
    object_privileges:
      - schema: app_schema
        object: fact_sales
        privileges:
          - SELECT

- name: Revoke temporary schema creation privilege
  exasol.exasol.exasol_grants:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    user: app_user
    state: absent
    system_privileges:
      - CREATE SCHEMA

exasol_user#

Use exasol.exasol.exasol_user to create, update, or remove one Exasol user. User names are exact Exasol identifier values, including names that need delimited-identifier syntax. A repeated state=present task is unchanged when the user already exists, except when update_password=always is used: Exasol cannot compare passwords, so this option always plans a password update for an existing user and reports changed=true.

state=absent drops an existing user. It does not cascade by default; specify cascade=true only when removing the user’s dependent objects is intended. LDAP distinguished names and passwords are sensitive and should come from a protected secret source.

- name: Create an application user
  exasol.exasol.exasol_user:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    name: app_user
    password: "{{ vault_app_user_password }}"

In check mode, the module reads user metadata and returns the planned SQL without changing Exasol. Its exists value reports the predicted final existence state and executed_queries contains the redacted planned SQL.

exasol_role#

Use exasol.exasol.exasol_role to create or remove one Exasol role. The module checks role metadata first, so repeated runs are idempotent. Role names are exact Exasol identifier values, including names that need delimited-identifier syntax.

state=absent removes an existing role. It does not cascade by default; set cascade=true only when removing dependent objects is intended.

- name: Create an application role
  exasol.exasol.exasol_role:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    name: app_reader

In check mode, the module reads role metadata and returns the planned SQL without changing Exasol. Its exists value reports the predicted final existence state.

exasol_schema#

Use exasol.exasol.exasol_schema to manage a physical schema and its intrinsic metadata. state=present creates a missing schema and reconciles only supplied new_name, owner, comment, and raw_size_limit values. Omitted mutable properties remain unmanaged, and a repeated task that already matches the requested state reports changed=false.

Set raw_size_limit: -1 to clear an existing raw-size limit. This emits ALTER SCHEMA ... SET RAW_SIZE_LIMIT = NULL only when a limit is currently set. Omitting raw_size_limit remains distinct: it leaves the limit unmanaged.

state=absent uses a non-cascading drop by default, so Exasol rejects the operation for a non-empty schema. Set cascade=true only when deleting all contained objects is intended.

- name: Create a reporting schema
  exasol.exasol.exasol_schema:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    name: reporting
    owner: app_reader
    comment: Reporting data

- name: Remove the reporting schema size limit
  exasol.exasol.exasol_schema:
    login_host: db.example.com
    login_user: "{{ vault_exasol_admin_user }}"
    login_password: "{{ vault_exasol_admin_password }}"
    name: reporting
    raw_size_limit: -1

In check mode, the module reads schema metadata and returns the same SQL plan it would execute without changing Exasol. Its exists value reports the predicted final existence state.