# 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