qontract (Queryable cONTRACT) is a collection of tools used to SREs to expose available managed services to application developer teams.
This repository comprises the server component, which is a GraphQL API server implemented in Typescript with the apollo-server-express package.
The JSON Schema Validation lives in a separate repo.
The schemas which are used for validation live in qontract-schemas.
The Reconcile loop is implementation specific. Any tool that conforms with the following patterns is considered a qontract reconcile tool:
- Retrieves desired state from the GraphQL API.
- Can retrieve the current state by inspecting the service to that needs to be configured.
- Is able to reconcile the service into the desired state from the discovered current state.
- Is idempotent.
- It can run with an option that only simulates what would happen, this called a plan or a dry-run.
An example of an implementation reconcile tools can be obtained from here: qontract-reconcile.
This server is configured via environment variables.
BUNDLE_SHA_TTL
: (not required) Expiration time for bundles. Defaults to20m
.LOAD_METHOD
: (required)fs
|s3
. Source of the data.AWS_ACCESS_KEY_ID
: (required ifLOAD_METHOD=s3
) AWS access key ID.AWS_SECRET_ACCESS_KEY
: (required ifLOAD_METHOD=s3
) AWS secret access key.AWS_REGION
: (required ifLOAD_METHOD=s3
) AWS region.AWS_S3_BUCKET
: (required ifLOAD_METHOD=s3
) AWS s3 bucket name.AWS_S3_KEY
: (required ifLOAD_METHOD=s3
) AWS s3 key name.
This server is able to store multiple bundle versions. Each time the data is reloaded, the new bundle is exposed on its new <sha>
on POST /graphqlsha/<sha>
. Previous shas will continue to work until they have expired.
The shas will expire after a certain amount of time:
- When the data is loaded for the first time, the expiration time is set to 20 minutes in the future (can be overriden by the
BUNDLE_SHA_TTL
environment variable). - Each time a sha is queried specifically the expiration is refreshed to the
BUNDLE_SHA_TTL
in the future again. This means that shas can be kept available forever by querying them before theBUNDLE_SHA_TTL
has passed. - The latest sha, which is the one pointed at by
POST /graphql
will never expire. - Shas are only expired when
GET /reload
is queried. - If
GET /reload
is called and there is no new data available, then no shas will be expired.
POST /graphqlsha/:sha
: the request body should contain the GraphQL query. The query will be directed at the specified bundle.POST /graphql
: the request body should contain the GraphQL query. The query will be directed at the latest bundle.GET /graphql
: redirects toPOST /graphql
.GET /sha256
: returns the sha of the latest bundle.GET /git-commit-info
: returns json doc with git commit information (commit sha and timestamp)GET /git-commit-info/:sha
: returns json doc with git commit information (commit sha and timestamp) for the specified bundleGET /cache
: returns a json with the cache information.GET /reload
: reloads data from the configured data source.GET /metrics
: prometheus metrics.GET /git-commit
: returns the git commit for the latest bundle. (deprecated, use git-commit-info instead)GET /git-commit/:sha
: returns the git commit for the specified bundle., use git-commit-info insteadGET /diff/:sha/:another_sha
: return the difference between two bundlesGET /diff/:sha/:another_sha/:filetype/:path
: return the difference for a singledatafile
orresourcefile
This server exposes prometheus metrics under /metrics
.
It includes some custom metrics:
qontract_server_reloads_total
: Number of reloads for qontract server.qontract_server_datafiles
: Number of datafiles for a specific schema.qontract_server_router_stack_layers
: Number of layers in the router stack.qontract_server_bundle_object_shas
: Number of shas cached by the application in the bundle object.qontract_server_bundle_cache_object_shas
: Number of shas cached by the application in the bundleCache object.
In addition, it also contains the metrics exposed by the express prometheus bundle. Note that the /graphqlsha/<sha>
path has been normalized to avoid cardinality explosion.
- Removing SHAs from the router stack is currently being done using an unsafe mechanism: splicing the private parameter
app._router.stack
which is unsupported and may cause issues. This functionality may break if the Express version is upgraded. However, the testing suite should catch this specific regression. The right solution for this is to replace the entire router, instead of removing the middleware. This has been discussed in this issue: expressjs/express#4436.
Although it is not required, it's recommended that you use yarn for install dependencies and running development scripts.
To install this projects dependencies to a local node_modules
directory:
yarn install
To run a process that watches for edits and rebuilds JavaScript from TypeScript:
yarn run watch
Or alternatively, you can run the TypeScript compilation once:
yarn build
The data files bundle is required to start the server. Once you're in the qontract-server
directory, run:
make bundle
Note that this requires Docker to be running on the host.
Optionally, if you want to specify the path for the app-interface repo or qontract-schemas repo on your local filesystem, you can use the parameter:
APP_INTERFACE_PATH
- (optional) path to a local app-interface repo (Default:$PWD/../../service/app-interface
).SCHEMAS_PATH
- (optional) path to a local qontract-schemas repo (Default:$PWD/../qontract-schemas
)
Example: To generate the bundle with a specific app-interface path:
make bundle APP_INTERFACE_PATH=/home/myuser/app-interface/
Create .env
file from example:
cp .env.example .env
Customize the .env
file as needed, for example:
LOAD_METHOD=fs
DATAFILES_FILE=./bundle/bundle.json
To run an instance of the qontract GraphQL console:
yarn run server
Specific instructions for CentOS 7:
# Install node10
sudo yum install centos-release-scl-rh
sudo yum install rh-nodejs10
# Install yarn (as root)
scl enable rh-nodejs10 bash
npm install -g yarn
# Enable node10 (as user in qontract-server git repo)
scl enable rh-nodejs10 bash
# Install qontract-server yarn modules
yarn install
# Build the JavaScript
yarn build
# Start the server
make run
Especially during PR checks, a qontract-server
with multiple bundles preloaded simplifies test infra setup. For this the env variable INIT_BUNDLES
to specify a comma separated list of bundle references of the following form
- fs://path/to/bundle
- s3://bundle-key
The s3 flavour relies on the AWS_*
env variables to specify the bucket and configure authentication. the specified bundle-key
is used as AWS_S3_KEY
.
The bundles listed in INIT_BUNDLES
are added to the qontract-server
in the order they are specified. This means that the bundle listed last is also the one returned by the /sha256
endpoint.
All code should follow the airbnb style guide, which is enforced by this projects lint script:
yarn run lint
While GQL does not define how filtering should work, it provides room for arguments to passed into queries. qontract-server
offers a generic filter
argument, that can be used to filter the resultset of a query.
query MyQuery($filter: JSON) {
clusters: clusters_v1(filter: $filter) {
...
}
}
The filter argument is a JSON document that can have the following content.
To filter on an fields value, use the following filter object syntax
"filter": {
"my_field": "my_value"
}
This way only resources with such a field and value are returned by the query.
Also works for null.
"filter": {
"my_field": null
}
To check if a property value is not equal to a certain value, use the ne
filter predicate
"filter": {
"my_field": {
"ne": "my_value"
}
}
This also works to check for missing values or references.
"filter": {
"my_field": {
"ne": null
}
}
Field values can be also compared towards a list of acceptable values.
"filter": {
"my_field": {
"in": ["a", "b", "c"]
}
}
This way only resources are returned where the respective field is a or b or c.
Filtering is also supported on nested structures of a resource.
"filter": {
"nested_resources": {
"filter": {
"nested_field": "nested_value"
}
}
}
Note this feature currently only support non array fields, the behaviour of filter list fields is undefined.