Ansible

Use the alpacax.alpacon collection to run playbooks through Alpacon. The alpacax.alpacon.exec connection uses the API directly; it does not require the Alpacon CLI.

Prepare the controller and token

The controller needs ansible-core 2.15 or later and Python 3.11 or later. Install the collection:

bash
ansible-galaxy collection install alpacax.alpacon

On a macOS controller, set this before running Ansible:

bash
export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES

Prepare an application and service token:

  1. Bind a server account to the application, or set its username and enable provisioning. The first command provisions the account on a Linux server with a current agent. Use that account as ansible_user.
  2. Give the application a global role covering the token’s scopes. Commands need command:create and command:read; server-name lookup also needs server:read. Grant file-upload access for module copies, and download access for tasks that fetch files.
  3. Request an alpst- service token and have a Superuser or the request’s approver approve it in the web console. Command and file-transfer scopes always wait for approval. Confirm that the token is active and enabled.
  4. After approval, add a server ACL for each target and file ACLs for the paths and directions the playbook uses. Ordinary modules are copied to the server before execution, so they need both command and upload permission.
  5. On Alpacon Cloud, have a Command ACL admins member add the commands under the token’s Pre-approved commands. This access is separate from the workspace role. Choose Connect if asked to connect. On self-hosted deployments, add command ACL rules matching the lines Ansible submits.

File transfer requires S3-backed storage. Allow the temporary paths used for module uploads as well as the paths your tasks manage. These token operations need no work session. See the setup and API reference for the account, role, and ACL steps.

Warning

Set ansible_user explicitly. The connection defaults to root, which a service token cannot use. Set it to the application’s server account, such as ci-deploy.

Run a minimal playbook

Save this as inventory.ini, replacing the server UUID and account with your values:

ini
[web]
web-01 alpacon_server_id=<server-uuid>
 
[web:vars]
ansible_connection=alpacax.alpacon.exec
ansible_user=ci-deploy

Set the workspace URL and inject the approved service-token key from your secret store. The value below is a placeholder:

bash
export ALPACON_URL=https://your-workspace.us1.alpacon.io
export ALPACON_TOKEN='alpst-REPLACE_WITH_SERVICE_TOKEN'

Save this as playbook.yml:

yaml
- hosts: web
  gather_facts: false
  tasks:
    - name: Who am I
      ansible.builtin.raw: whoami

Run it:

bash
ansible-playbook -i inventory.ini playbook.yml

The command runs as ci-deploy. The raw task submits whoami without uploading a module, so a command rule for whoami covers this check. Direct root access is off by default. Leaving ansible_user at its default produces a 400 response containing "code": "workspace_direct_root_disabled". The error body has this shape (... stands for the validation messages):

{"code": "workspace_direct_root_disabled", "field_errors": {"non_field_errors": [...]}}

Only in a workspace that allows direct root does this request reach the application_system_user_not_found check. Changing an ACL to allow root does not make it an application-owned account.

How Ansible runs modules

An ordinary module task submits wrapper commands to find the account home, create a random directory under ~/.ansible/tmp/, discover Python, set permissions, execute the uploaded module, and remove the temporary files. For example, the command module uploads AnsiballZ_command.py. Each submitted wrapper line needs a matching command rule, and the module upload needs a file ACL for its temporary path. A narrow rule for whoami does not cover those module tasks. Wrapper lines vary with Ansible and the target; inspect them in a controlled live run before using the playbook in production.

Connection options

Set options as inventory variables, environment variables, or in ansible.cfg where supported. The INI names below belong under [alpacon_connection], except remote_user, which belongs under [defaults].

Inventory variableEnvironment variableINI nameDefault or purpose
alpacon_urlALPACON_URLurlRequired workspace URL
alpacon_tokenALPACON_TOKENtokenRequired alpst- service token
alpacon_server_id——Exact server UUID; takes precedence over a name
alpacon_server_name——When omitted, ansible_host if set, otherwise the inventory hostname
alpacon_approval_timeoutALPACON_APPROVAL_TIMEOUTapproval_timeout300 seconds; 0 or less fails immediately when approval is needed
alpacon_ssl_verifyALPACON_SSL_VERIFYssl_verifytrue
alpacon_ca_certALPACON_CA_CERTca_certCA certificate path
ansible_user or ansible_ssh_user—remote_userDefaults to root; set the application’s account

Without a server ID, the connection looks up the name. More than one match produces Multiple servers match name ...; no visible match produces Server '<name>' not found in alpacon.. Use a server ID to resolve ambiguity, and check the token’s server access if the target is not found.

Privilege escalation with become

With become, Ansible still connects as the application’s account and submits a sudo command. On Alpacon Cloud, service-token sudo is allowed below Critical, including unassessed commands, and refused at Critical unless the workspace is Advisory. In Advisory, it is not refused for risk at any level, and the decision is recorded. Outside Advisory, a workspace token_exec rule can tighten the risk threshold. The workspace sudo setting must be on; otherwise sudo is refused with workspace_sudo_with_mfa_disabled. On a default self-hosted installation, every service-token sudo is refused with sudo_command_not_authorized. Sudo policies do not apply to service tokens, and sudo does not wait for a human reviewer.

The command entry must cover the submitted line, including its sudo prefix. Ansible adds temporary paths and a generated marker, so review the actual line when configuring command access. Do not grant a broad sudo * entry just to make a playbook pass. become is unavailable with this token-only connection on a default self-hosted installation.

Approval waits and results

When a command waits for approval, the connection prints:

alpacax.alpacon.exec: command <id> is awaiting approval in the alpacon console; waiting up to <n>s for a decision

Have an approver review it in the web console. alpacon_approval_timeout controls that wait separately from the 300-second execution timeout.

ResultReturn code and output
Successrc: 0
Execution timeoutrc: 124, stderr Command execution timed out
Other non-success resultrc: 1, with the status such as denied, rejected, or awaiting_approval on stderr
Denied or rejectedrc: 1, stdout Command was rejected by the approval policy
Approval wait timeoutrc: 1, stderr awaiting_approval; stdout warns do not resubmit, that would queue a duplicate run

On Cloud, Command was rejected by the approval policy can also mean a missing command rule. The API accepts the command with 201, then it ends rejected with rejection_cause command_acl_denied or command_acl_unavailable. Read rejection_cause on the command before changing approval settings. On self-hosted deployments, a command ACL refusal returns 403 api_token_acl_not_allowed.

If the approval wait times out, do not resubmit: the original command remains queued, and another submission can run it twice. Review the existing command in the console.

Module tasks submit several commands, and result polling also consumes requests. Even a small fleet can reach the default service-token limit of 1000/hour. The connection does not retry HTTP 429. Reduce concurrency and wait for the Retry-After delay. Before retrying a task, check whether its command was already accepted; a polling failure does not mean the command failed, and resubmitting it can duplicate execution.

For HTTP errors, expired credentials, and refused accounts, see Automation failure modes.

Last updated: