The missing developer toolkit for Gallagher Command Centre
Gallagher Security manufacture a variety of perimeter security products. At the hear of these is the Command Centre software. Command Centre is deployed locally (in simplistic terms, the complexity varies for every use case). Version 8.6
introduced a REST API which allows you to interact with the system via HTTP requests locally or via Gallagher's Cloud API Gateway which eliminates the need for maintaining proxies and VPNs.
Our Python Toolkit focuses on enhancing the developer experience (DX) around the REST API. In principle we provide the following:
asyncio
support) to extend the CC functionality.[!NOTE]\ This project is NOT affiliated with Gallagher Security. All trademarks are the property of their respective owners.
While Gallagher maintain a set of Swagger definitions for their API, they are primarily intended to generate the documentation published on Github. They use a tool called Spectacle. Gallagher explicitly state that the Swagger definitions are not intended to be used to generate code. Due to this the API client is hand built and not auto-generated.
[!IMPORTANT]\ Due to custom annotations the YAML files will not parse with any standard parser.
Everything this project provides hinges upon our Python SDK, designed to enhance the developer experience. It's design is highly opinionated from our experience in building APIs, we ensure conformance with Gallagher software design interfaces.
[!TIP]\ If you've worked with stripe-python the syntax may feel familiar.
If you are using one of our user facing tools, it's not important for you to understand how the SDK works, however since it underpins everything, here's a rather sample example:
# Import core python libs
import os
import asyncio
# Import the client and models
from gallagher import (
cc,
)
from gallagher.dto.summary import (
CardholderSummary,
)
from gallagher.cc.cardholders import (
Cardholder,
)
# Set the API key from the environment
api_key = os.environ.get("GACC_API_KEY")
cc.api_key = api_key
# Async support gives us back a coroutine
ch_coro = Cardholder.list()
# Run the coroutine to get the cardholder
cardholders = asyncio.run(ch_coro)
cardholder = cardholders.results[0]
# This is now a pydantic object
type(cardholder) == CardholderSummary
# Print out some details from the object
cardholder.href
cardholder.first_name
[!IMPORTANT]\ Gallagher infrastructure deals with perimeter security. We take this extremely seriously and providing a complete test suite to provide that our software meets all standards. These tests constantly run against our demo command centre hosted on the cloud.
The rest of the README touches upon each of the tools we provide. If you like what you see so far we recommend you head over to our documentation.
Our CLI is designed to automate custom workflows via scripts. Inspired by the greatest Unix tools out there, it does one thing and it does it well, leaving you to integrate it into a pipeline. The utility is able to speaking machine readable formats like JSON, YAML and CSV as well as producing formatted output.
Here's an example of fetching the details of a cardholder
:
(gallagher-py3.11) ➜ gallagher git:(alpha-3) gala ch get 8272
person
id 8272
first_name Jerry
last_name Zurcher
short_name None
description None
authorised yes
disable_cipher_pad no
division 2
hrefs
edit edit
Shillelagh is a Python library that allows you to interact with REST APIs as if they were SQL databases, including the ability to provide a SQLAlchemy dialect
allowing you to treat endpoints as a virtual table.
Assuming you had the SQL extensions installed, a simplistic example of querying Cardholders from the command would look like this:
🍀> SELECT * FROM "https://commandcentre-api-au.security.gallagher.cloud/api/cardholders" WHERE id=8427;
which would return a result set of:
first_name last_name authorised id
------------ ----------- ------------ ----
Cammy Albares True 8427
(1 row in 0.23s)
The Gallagher API the principles of HATEOAS which ensures that the API is self-descriptive and future proof.
A href
attribute provides a the destination of referenced objects in the responses. These are full qualified and will be prefixed with the server origin i.e if you are using the Cloud Gateway then all your URLs will be prefixed with the appropriate gateway's address.
These appear in various forms, starting from as simple as the href
itself:
"cardholders": {
"href": "https://localhost:8904/api/access_groups/352/cardholders"
}
through to self recursive references (where the data is nested) with additional attributes:
"parent": {
"href": "https://localhost:8904/api/access_groups/100",
"name": "All R&D"
}
[!CAUTION]\ Following the design patterns outlined by HATEOAS, you must never hardcode any URLs. You should hit the base API URL which returns the
hrefs
of all other resources. If you are using the Python SDK, then you don't have to worry about this, the client will handle this for you.
This API client primarily depends on the following libraries:
We use Taskfile to automate running tasks.
The project provides a comprehensive set of tests which can be run with task test
. These tests do create objects in the Command Centre, we advice you to obtain a test license.
[!IMPORTANT] It's not recommended to run tests against a production system.
There are three types of schema definitions, each one of them suffixed with their intent:
References
to other objects, they using contain a href
and possibly additional meta data such as a name
or id
href
, they compound on the Summary
schema and add additional attributesSummary
objects with other paths like next
and previous
for pagination and updates
for polling resultsIn summary the properties of each are as follows:
Refs
are the minimal pathway to an objectSummary
builds on a Ref
and provides a subset of the attributesDetail
builds on a Summary
and provides the full set of attributesResponse
encapsulates a collection of Summary
objects, they typically have next
and previous
paths for paginationPayload
are verbose and match the schema definition on the documentationEach resource
endpoint subclasses the APIEndpoint
which marks a resource as fetchable
, queryable
, creatable
, updatable
and deletable
. This is determined by the configuration defined using an EndpointConfig
class.
[!TIP] The above is meant to be a summary, please see our documentation for more details.
Our schemas
provide a set of Mixins
that are used to construct the Models. These are repeatable patterns that need not be repeated. The typical patter would be to subclass from the Mixins
e.g:
from .utils import AppBaseModel, IdentityMixin, HrefMixin
class AccessGroupRef(
AppBaseModel,
HrefMixin
):
""" Access Groups is what a user is assigned to to provide access to doors
"""
name: str
where the HrefMixin
(see also OptionalHrefMixin
for use where the href
is not always present) provides the href
attribute:
class HrefMixin(BaseModel):
""" Href
This mixin is used to define the href field for all
responses from the Gallagher API.
"""
href: str
These Mixin
classes can also be used to declare attributes that seek to use the same pattern:
class DivisionDetail(
AppBaseModel,
IdentityMixin,
):
""" Defines a Division on the Gallagher Command Centre
"""
name: str
description: Optional[str] = None
server_display_name: Optional[str] = None
parent: OptionalHrefMixin = None
### Schemas
Our `schemas` provide a set of `Mixins` that are used to construct the Models. These are repeatable patterns that need not be repeated. The typical patter would be to subclass from the `Mixins` e.g:
```python
from .utils import AppBaseModel, IdentityMixin, HrefMixin
class AccessGroupRef(
AppBaseModel,
HrefMixin
):
""" Access Groups is what a user is assigned to to provide access to doors
"""
name: str
where the HrefMixin
provides the href
attribute:
class HrefMixin(BaseModel):
""" Href
This mixin is used to define the href field for all
responses from the Gallagher API.
"""
href: str
These Mixin
classes can also be used to declare attributes that seek to use the same pattern:
class DivisionDetail(
AppBaseModel,
IdentityMixin,
):
""" Outlines the definition of a Division on the Gallagher Command Centre
"""
name: str
description: Optional[str]
server_display_name: str
parent: Optional[HrefMixin]
where parent
is simply an href
without any other attributes. In the cases where these attributes have more than just an href
we defined Reference
classes:
class AccessGroupRef(
AppBaseModel,
HrefMixin
):
""" Access Groups is what a user is assigned to to provide access to doors
"""
name: str
and use them to populate the attributes:
class VisitorTypeDetail(
AppBaseModel,
IdentityMixin
):
"""
"""
access_group : AccessGroupRef
host_access_groups: list[AccessGroupSummary]
visitor_access_groups: list[AccessGroupSummary]
In this example the AppGroupRef
has a name
attribute which is not present in the HrefMixin
class.
Please see the schema section for naming conventions for
schema
classes
where parent
is simply an href
without any other attributes. In the cases where these attributes have more than just an href
we defined Reference
classes:
class AccessGroupRef(
AppBaseModel,
HrefMixin
):
""" Access Groups is what a user is assigned to to provide access to doors
"""
name: str
and use them to populate the attributes:
class VisitorTypeDetail(
AppBaseModel,
IdentityMixin
):
"""
"""
access_group : AccessGroupRef
host_access_groups: list[AccessGroupSummary]
visitor_access_groups: list[AccessGroupSummary]
In this example the AppGroupRef
has a name
attribute which is not present in the HrefMixin
class.
Please see the schema section for naming conventions for
schema
classes
The following are resources that were discoverd during the design and development of these tools. Not all of them are in use by the toolkit, they were discovered as the library evolved.
[!TIP] Following are Python libraries that I have found during the development of the Gallagher tools. They are not necessarily in use at the moment but a reference in case we need the functionality.
Distributed under the MIT License except Artwork and Branding assets.