# Notes on buildkite APIs
## buildkite-jobify
### Config
Takes a TOML config with:
- organization
- api token
- an array of the pipelines to watch
- namespace to create jobs in
- optional cluster identifier, agents can specify clusters?
### Structure
**Monitor** is responsible for periodically fetching all the pipelines, polling them for builds, and sending them to the Jobifier. Uses GraphQL API to poll. It keeps track of *known* builds,
**Jobifier** is responsible for receiving builds and turning them into jobs.
**Scheduler** takes jobs from the monitor and dispatches them to the jobifier
### GraphQL API
Example query for queued/failed builds and their jobs:
```graphql!
query GetBuilds($id: ID = "UGlwZWxpbmUtLS0wMTg0NjI3NC0wOWJjLTRjN2EtYmI5MC1jYjMxN2U1NjlmZTc=") {
node(id: $id) {
__typename
... on Pipeline {
builds(first: 1, state: [RUNNING, FAILING]) {
count
edges {
node {
uuid
message
state
commit
metaData(first: 10) {
count
edges {
node {
key
value
}
}
}
jobs(first: 1) {
count
edges {
node {
__typename
... on JobTypeCommand {
uuid
label
state
agentQueryRules
exitStatus
agent {
name
}
env
}
}
}
pageInfo {
hasNextPage
}
}
}
}
}
}
}
}
```
Response:
```json!
{
"data": {
"node": {
"__typename": "Pipeline",
"builds": {
"count": 1,
"edges": [
{
"node": {
"uuid": "018476d3-6f90-441a-8b80-7c525e1c2eb8",
"message": "Update README.md",
"state": "RUNNING",
"commit": "7b71ee28a15ef298fb511fb025bfbbdb5b671a27",
"metaData": {
"count": 0,
"edges": []
},
"triggeredFrom": null,
"jobs": {
"count": 5,
"edges": [
{
"node": {
"__typename": "JobTypeCommand",
"uuid": "018476d4-3ba2-4e47-adec-3cdf277190da",
"label": ":golang:",
"state": "SCHEDULED",
"agentQueryRules": [
"queue=3462986656"
],
"exitStatus": null,
"agent": null,
"env": [
"CI=true",
"BUILDKITE=true",
"BUILDKITE_ORGANIZATION_SLUG=superorbital",
"BUILDKITE_PIPELINE_SLUG=buildkite-gh-actions",
"BUILDKITE_PIPELINE_NAME=buildkite-gh-actions",
"BUILDKITE_PIPELINE_ID=01846274-09bc-4c7a-bb90-cb317e569fe7",
"BUILDKITE_PIPELINE_PROVIDER=github",
"BUILDKITE_PIPELINE_DEFAULT_BRANCH=main",
"BUILDKITE_REPO=https://github.com/benmoss/buildkite-gh-actions",
"BUILDKITE_BUILD_ID=018476d3-6f90-441a-8b80-7c525e1c2eb8",
"BUILDKITE_BUILD_NUMBER=33",
"BUILDKITE_BUILD_URL=https://buildkite.com/superorbital/buildkite-gh-actions/builds/33",
"BUILDKITE_BRANCH=main",
"BUILDKITE_TAG=",
"BUILDKITE_COMMIT=7b71ee28a15ef298fb511fb025bfbbdb5b671a27",
"BUILDKITE_MESSAGE=Update README.md",
"BUILDKITE_SOURCE=ui",
"BUILDKITE_BUILD_AUTHOR=",
"BUILDKITE_BUILD_AUTHOR_EMAIL=",
"BUILDKITE_BUILD_CREATOR=Ben Moss",
"BUILDKITE_BUILD_CREATOR_EMAIL=ben.moss@superorbital.io",
"BUILDKITE_REBUILT_FROM_BUILD_ID=",
"BUILDKITE_REBUILT_FROM_BUILD_NUMBER=",
"BUILDKITE_PULL_REQUEST=false",
"BUILDKITE_PULL_REQUEST_BASE_BRANCH=",
"BUILDKITE_PULL_REQUEST_REPO=",
"BUILDKITE_TRIGGERED_FROM_BUILD_ID=",
"BUILDKITE_TRIGGERED_FROM_BUILD_NUMBER=",
"BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG=",
"BUILDKITE_JOB_ID=018476d4-3ba2-4e47-adec-3cdf277190da",
"BUILDKITE_LABEL=:golang:",
"BUILDKITE_COMMAND=go test -v .",
"BUILDKITE_ARTIFACT_PATHS=",
"BUILDKITE_RETRY_COUNT=3",
"BUILDKITE_TIMEOUT=false",
"BUILDKITE_STEP_KEY=",
"BUILDKITE_STEP_ID=018476d3-7de3-4e5b-82c8-c3801a0a46b4",
"BUILDKITE_PROJECT_SLUG=superorbital/buildkite-gh-actions",
"BUILDKITE_PROJECT_PROVIDER=github",
"BUILDKITE_SCRIPT_PATH=go test -v ."
]
}
}
],
"pageInfo": {
"hasNextPage": true
}
}
}
}
]
}
}
}
}
```
## buildscaler
Uses the agent `/metrics` endpoint to get info about agents/builds.
Example request:
```bash!
http https://agent.buildkite.com/v3/metrics Authorization:"Token $TOKEN"
```
Example response:
```json!
{
"agents": {
"busy": 1,
"idle": 0,
"queues": {
"default": {
"busy": 1,
"idle": 0,
"total": 1
}
},
"total": 1
},
"jobs": {
"queues": {
"3462916696": {
"running": 1,
"scheduled": 0,
"total": 1,
"waiting": 0
}
},
"running": 1,
"scheduled": 0,
"total": 1,
"waiting": 0
},
"organization": {
"slug": "superorbital"
}
}
```
## What we need
The GraphQL API is what we need, since we can get at the full job spec before it has even run.
We can use this to make decisions about whether we run it in a default agent container, using the job's PodSpec, or not at all.
## Open questions
- Polling first seems like the simplest option
- A webhook is possible, but we'd need some way of getting the result