Category: API’s

How to pass an API key with Ansible

https://chronosphere.io/ – Third Party Cloud Monitoring Solution

Chronocollector: – https://github.com/Perfect10NickTailor/chronocollector

This role deploys the chronocollector management service which sends the data to domain.chronosphere.io For those of you who don’t know what it is. Its basically a cloud monitoring tool that scrapes data on your instances and then you can create dashboards or even export the data to promethus to make it look pretty and easy to read. You will likely pay for subscription, they will give you a subdomain which becomes your gateway address (domain.chronosphere.io)

Special note: You then need to deploy the node_exporter to push to the hosts you want scraped. That is a separate playbook and stupid easy.

This role will download the latest collector
It will install the latest collector
It will check to see if the ‘service’ its added to systemd, if nots.. adds it, if the service is there, it will move on and simply start the service.

#nowthatsjustfunny: So its debatable on how to approach passing {{ api_keys }} in a scalable and secure way. A lot of people create an “ansible vault encrypted variable”. This is so that when they push their code to their git repos. The {{ api_key }} isn’t exposed to someone simply glancing by the code. The issue with this approach is now you have to remember a vault password to pass to ansible, so it can decrypt the {{ api_key }} to pass, inorder for it to work when you run the playbook.(LAME)

.

#nowthatsjustcool: So just for the purposes of this post and for fun. I wrote it so that you can simply pass the {{ api_key }} during runtime. This way instead of being prompted for the vault-pass, you are prompted for the api_key to pass as a variable when you run the book. This gets rid of the need to setup a encrypted variable in your code entirely. Everyone has their own way of doing things, but I tend to think outside the box, so it always way more fun to be different in how you think.

.

Ansible Operational Documentation

How to use this role:

1.You must first download the git repository
a.git clone git@github.com:Perfect10NickTailor/chronocollector.git
2.Under your user you will see a directory called chronocollector cd into this directory here you will see the defaults/main.yml where you can see what you can pass to groups_vars & host_vars
b.cd chronocollector

.

3.Next you want edit the hosts.client inside your ansible/inventory/dev/hosts.client

Example file: hosts.dev or hosts.staging

c.Put your server under the appropriate group inside the file and save
i.Testmachine1 ansible_host=192.168.60.10

Running your playbook:

1.You must run your play book from inside parent ansible directory

.

2.Now there is a playbook called chronocollector.yml in the ansible directory which simply calls the chronocollector role inside the ansible/roles/chronocollector directory, where the role should be living.

Example: of ansible/chronocollector.yml

hosts: all

  gather_facts: no

  vars_prompt:

  – name: api_key

    prompt: Enter the API key

  roles:

    – role: chronocollector

.

Command:

ansible-playbook -i inventory/dev/hosts.dev chronocollector.yml -u nickadmin -Kkb –ask-become –limit=’testmachine3′

-i : This flag tells ansibe-playbook command which hosts file to use, these are always defined by customer like hosts.dev or hosts.staging
-u : this is the ssh_user you will be connecting to the servers with
-Kkb : this tells ansible that you will be using sudo su – for the ssh_user when running all role/tasks
-ask-beocme : is saying become root
-limit=’server’ : this allows you to segement which server you want to run the playbook against.

.

Successful run:

.

Notice: It asks you for the API key at runtime.

ntailor@jumphost:~/ansible2$ ansible-playbook -i ansible/inventory/dev/hosts.dev chronocollector.yml -u nicktadmin -Kkb –ask-become –limit=’testmachine3′

SSH password:

BECOME password[defaults to SSH password]:

Enter the API key:

.

PLAY [all] ***************************************************************************************************************************************************************************************************************

.

TASK [chronocollector : download node collector] *************************************************************************************************************************************************************************

ok: [testmachine3]

.

TASK [chronocollector : move collector to /usr/local/bin] ****************************************************************************************************************************************************************

ok: [testmachine3]

.

TASK [chronocollector : mkdir directory /etc/chronocollector] ************************************************************************************************************************************************************

ok: [testmachine3]

.

TASK [chronocollector : Copy default config.yml to /etc/chronocollector/] ************************************************************************************************************************************************

ok: [testmachine3]

.

TASK [chronocollector : Touch again the same file, but do not change times this makes the task idempotent] ***************************************************************************************************************

changed: [testmachine3]

.

TASK [chronocollector : Ensure API key is present in config file] ********************************************************************************************************************************************************

changed: [testmachine3]

.

TASK [chronocollector : Change file ownership, group and permissions apitoken file to secure it from prying eyes other than root] ****************************************************************************************

changed: [testmachine3]

.

TASK [chronocollector : Check that the service file /etc/systemd/system/collector.service exists] ************************************************************************************************************************

ok: [testmachine3]

.

TASK [chronocollector : Include add systemd task if service file does not exist] *****************************************************************************************************************************************

included: ansible/roles/chronocollector/tasks/systemd.yml for testmachine3

.

TASK [chronocollector : Create startup file for collector in systemd] ****************************************************************************************************************************************************

changed: [testmachine3]

.

TASK [chronocollector : Create systemd collector.service] ****************************************************************************************************************************************************************

changed: [testmachine3]

.

TASK [chronocollector : check whether custom line exists] ****************************************************************************************************************************************************************

changed: [testmachine3]

.

TASK [chronocollector : Start Collector Service via systemd] *************************************************************************************************************************************************************

changed: [testmachine3]

.

TASK [chronocollector : Show status of collector from systemd] ***********************************************************************************************************************************************************

changed: [testmachine3]

.

TASK [chronocollector : debug] *******************************************************************************************************************************************************************************************

ok: [testmachine3] => {

“status.stdout”: ” Active: failed (Result: exit-code) since Thu 2022-05-19 10:31:49 BST; 315ms ago”

}

.

PLAY RECAP ***************************************************************************************************************************************************************************************************************

testmachine3 : ok=15 changed=8 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

How to call a json rest API using Ansible        

So a very useful thing to understand is rest api’s and how to call them as a lot of organisations have these and want to integrate them into automation, a popular method is the http method

They are very simple calls { GET, POST, PUT, DELETE, PATCH }

For the sake of this post. Im going to use commvault public api’s https://api.commvault.com/

You will need to two things.

  1. The api endpoint which is usually an http url

Example:

  1. http://WebConsoleHostName/webconsole/api/CommServ/Failover
  1. The raw  json body of the of the api

Example:

{

    "csFailoverConfigInfo": {

        "configStatus": 0,

        "isAutomaticFailoverEnabled": false

    }

}

Now keep in mind if you are using an api that requires a login. In order for it to work, you will need to store the auth token to pass later to the last task later for the api call to work as intended. You can look at one of my other posts under vmware, where i used a http login to handle the tasks later, as a reference.

You can call these preliminary task as includes to store the token.

It will look something like this before it gets to the api task. You can also just do it all one on book if you wanted to. But for the purposes of this post. Im just giving ya highlevel.

- name: Login task

  include_role:

    name: commvault_login

    tasks_from: login.yml

- name: Setfact for authtoke

  set_fact:

    authtoken: "{{ login_authtoken }}"

  delegate_to: localhost

Now in order for you to pass json api to ansible. You will need to convert the json raw body into yaml format. You can use visual studio code plugins or a site like https://json2yaml.com/

So if we are to use the above raw json example it would look like this

csFailoverConfigInfo:

  configStatus: 0

  isAutomaticFailoverEnabled: false

So now we want to pass this information to the task in the form of a variable. A really cool thing with ansible and this type of action. Is you can create a variable name and simply pass the new yaml converted body right below the varible. You can pass this as extra-vars or create a group variable with the same name and use that.

For those you who use tower passing them as extra-vars to test something can be a pain, since it doesn’t allow you to change the passed vars and rerun the previous run just used, you have to start all over. So I prefer the command line way as its easier to be agile

disable_api_body:

csFailoverConfigInfo:

  configStatus: 0

  isAutomaticFailoverEnabled: false

So now we ansible to use the rest api with ansible. You create a task that after the login is run and the token is stored inside as a fact. It run the following task, in our case this call will be a POST. It will post the headers to the url which will disabled commvault live_sync which is essentially commvault failover redundancy for the backup server itself.

- name: Disable Commvault livesync 

  uri:

    url: http://{{ commvault_primary }}/webconsole/api/v2/CommServ/Failover

    method: POST

    body_format: json

    body: "{{ disable_api_body }}"

    return_content: true

    headers:

      Accept: application/json

      Content-Type: application/json

      Authtoken: "{{ login_authtoken }}"

    status_code: 200

    validate_certs: false

  register: disable_livesync

  retries: "4"

  delay: "10"

  delegate_to: localhost

- debug: 
    var: disable_livesync
     

When you run the book and your have an active failover setup correctly with commvault. In the command center under the control panel you should see livesync. If you click on this you should see either it is checked or unchecked.

0