Python Remote API¶
The Adminapi provides a python module which can talk to the Serveradmin via an API. It provides functions for querying servers, modifying their attributes, triggering actions (e.g. committing nagios) etc.
Warning
This is only a draft. The API might change.
Authentication¶
Every script that uses the module must authorize itself before using the API. You need to generate a so called application for every script in the admin interface of the serveradmin. This has several benefits over using a generic password:
Logging changes that were done by a specific script
Providing a list with existing scripts which are using the API
Possibility to revoke an authentication token without changing every script
The API allows authentication of an application either via public-key cryptography (ssh-keys) or pre shared keys (passwords). Using the new public-key style authentication has even more benefits:
An application can have multiple public keys, making it easier to change them
Adminapi can sign requests via keys in your local or forwarded ssh-agent
Keys in the ssh-agent can be password protected on disk and decrypted only inside the agent. Adminapi never even sees the private part of the key
Serveradmin only knows the public part of the key, while an admin can read all pre shared keys from serveradmin and use them to impersonate others.
To authenticate yourself via an ssh key you have to add the public part to an application in serveradmin. You can then either add the private key to your ssh-agent or export the SERVERADMIN_KEY_PATH environment variable to the path of the private key:
# Use ssh-agent, passwords protected keys are supported
ssh-add ~/.ssh/id_rsa
# Use environment vairable, passwords protected keys are _not_ supported
export SERVERADMIN_KEY_PATH=~/.ssh/id_rsa
Note that for ed25519 key support both adminapi and serveradmin must have paramiko 2.2 or newer installed.
To authenticate yourself via a pre shared key you need to set the SERVERADMIN_TOKEN environment variable or create a file called .adminapi in the home folder of your user:
# Use environment variable (Useful for transient jobs such as Jenkins)
export SERVERADMIN_TOKEN=MLifIK9FMQTaFDneDneNg30pb
# Use .adminapirc file in home folder
echo "auth_token=MLifIK9FMQTaFDneDneNg30pb" >> ~/.adminapirc
chmod 0600 ~/.adminapirc
The order of prevalence is:
SERVERADMIN_KEY_PATH if set
SERVERADMIN_TOKEN if set
~/.adminapirc if present
ssh-agent if present
Note that we try to authenticate with all keys in the agent. If multiple keys, belonging to different applications, match you will get a permission denied. This is because the associated apps likely have different permissions and we don’t want to guess which to enforce. Trying to authenticate with more than 20 keys will also be denied to prevent a DOS.
Querying and modifying servers¶
Using the dataset
module you can filter servers by given criteria and
modify their attributes.
Basic queries¶
You can use the adminapi.dataset.Query
function to find servers which
match certain criteria. See the following example which will find all
webservers of Tribal Wars:
from adminapi.dataset import Query
hosts = Query({'servertype': 'vm', 'game_function': 'web'})
for host in hosts:
print(host['hostname'])
The Query class takes keyword arguments which contain the filter conditions. Each key is an attribute of the server while the value is the value that must match. You can either use strings, integers or booleans for exact value matching. All filter conditions will be ANDed.
More often you need filtering with more complex conditions, for example regular expression matching, comparison (less than, greater than) etc. For this kind of queries there is a filters modules which defines some filters you can use. The following example will give you all Tribal Wars webservers, which world number is between 20 and 30:
from adminapi.filters import All, GreaterThan, LessThan
hosts = Query({
'servertype': 'vm',
'game_function': 'web',
'game_world': All(GreaterThan(20), LessThan(30)),
})
Accessing and modifying attributes¶
Each server is represented by a server object which allows a dictionary-like
access to their attributes. This means you will have the usual behaviour of
a dictionary with methods like keys()
, values()
, update(...)
etc.
You can get server objects by iterating over a query or by calling
get()
on the query. Changes to the attributes are not directly
committed. To commit them you must call commit()
on the query.
Here is an example which cancels all servers for Seven Lands:
hosts = Query({'servertype': 'hardware'}, ['canceled'])
for host in hosts:
hosts['canceled'] = True
hosts.commit()
Another example will print all attributes of VM objects and check for the
existence of the function
attribute:
vm = Query().new_object('vm')
for attr, val in vm.items():
print('{} => {}'.format(attr, val))
if 'function' not in techerror:
print('Something is wrong!')'
Multi attributes are stored as instances of adminapi.dataset.MultiAttr
,
which is a subclass of set. Take a look at set
for the available
methods. See the following example which iterates over all additional IPs and
adds another one:
techerror = Query({'hostname': 'techerror.support.ig.local'}, ['additional_ips']).get()
for ip in techerror['additional_ips']:
print(ip)
techerror['additional_ips'].add('127.0.0.1')
Warning
Modifying attributes of a server object that is marked for deleting will
raise an exception. The update()
function will skip servers that
are marked for deletion.
Query Reference¶
The adminapi.dataset.Query
function returns a query object that
supports iteration and some additional methods.
- class Query¶
- __iter__()¶
Return an iterator that can be used to iterate over the query. The result itself is cached, iterating several times will not hit thedatabase again. You usually don’t call this function directly, but use the class’ object in a for-loop.
- __len__()¶
Return the number of servers that where returned. This will fetch all results.
- get()¶
Return the first server in the query, but only if there is just one server in the query. Otherwise, you will get an exception. #FIXME: Decide kind of exception
- commit_state()¶
Return the state of the object.
- commit()¶
Commit the changes that were done by modifying the attributes of servers in the query. Please note: This will only affect servers that were accessed through this query!
- rollback()¶
Rollback all changes on all servers in the query. If the server is marked for deletion, this will be undone too.
- delete()¶
Marks all server in the query for deletion. You need to commit to execute the deletion.
Warning
This is a weapon of mass destruction. Test your script carefully before using this method!
- update(**attrs)¶
Mass update for all servers in the query using keyword args. Example: You want to cancel all Seven Land servers:
Query({'servertype': 'hardware'}).update(canceled=True)
This method will skip servers that are marked for deletion.
You still have to commit this change.
Server object reference¶
The reference will only include the additional methods of the server object.
For documentation of the dictionary-like access see dict
.
- class DatasetObject¶
- old_values¶
Dictionary which contains the values of the attributes before they were changed.
- is_dirty()¶
Return True, if the server object has uncomitted changes, False otherwise.
- is_deleted()¶
Return True, if the server object is marked for deletion.
- delete()¶
Mark the server for deletion. You need to commit to delete it.
Making API calls¶
API calls are split into several groups. To call a method you need to get a group object first. See the following example for getting a free IP:
# Do authentication first as described in section "Authentication"
from adminapi import api
nagios = api.get('nagios')
nagios.commit('push', 'john.doe', project='techerror')