Build a Custom API Client
Introduction
This tutorial teaches you how to create a custom client for the Dagger GraphQL API in a programming language of your choice. You will learn how to:
- Create a custom client for the Dagger GraphQL API
- Connect to the Dagger GraphQL API and run your custom client with the Dagger CLI
Requirements
You can create a Dagger GraphQL API client in any programming language. This tutorial demonstrates the process in the following languages:
- Rust, using the gql_client library (MIT License)
- PHP, using the php-graphql-client library (MIT License)
This tutorial assumes that:
- You have a basic understanding of the Rust or PHP programming languages. If not, read the Rust tutorial or read the PHP tutorial.
- You have a development environment for Rust 1.65 (or later) or PHP 8.1 (or later). If not, install Rust or install PHP.
- You have Docker installed and running on the host system. If not, install Docker.
- You have the Dagger CLI installed on the host system. If not, install the Dagger CLI.
Step 1: Select and install your GraphQL client library
The first step is to identify available GraphQL clients for your chosen programming language and select one that fits your requirements. GraphQL has a large and growing list of client implementations in over 20 languages.
Create a new directory for the project and install the client as follows:
- Rust
- PHP
mkdir my-project
cd my-project
cargo init
cargo add gql_client@1.0.7
cargo add serde_json@1.0.89
cargo add tokio@1.22.0 -F full
cargo add base64@0.13.1
mkdir my-project
cd my-project
composer require gmostafa/php-graphql-client
Step 2: Create an API client
Once the client library is installed, create an API client as described below.
- Rust
- PHP
Add the following code to src/main.rs
:
use base64::encode;
use gql_client::Client;
use serde_json::Value;
use std::collections::HashMap;
use std::env;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let port = env::var("DAGGER_SESSION_PORT").expect("$DAGGER_SESSION_PORT doesn't exist");
let token = env::var("DAGGER_SESSION_TOKEN").expect("$DAGGER_SESSION_TOKEN doesn't exist");
let query = r#"
query {
container {
from (address: "alpine:latest") {
withExec(args:["uname", "-nrio"]) {
stdout
}
}
}
}
"#;
let mut headers = HashMap::new();
headers.insert(
"authorization",
format!("Basic {}", encode(format!("{}:", token))),
);
let client = Client::new_with_headers(format!("http://127.0.0.1:{}/query", port), headers);
let data = client.query_unwrap::<Value>(query).await.unwrap();
println!(
"{}",
data["container"]["from"]["withExec"]["stdout"]
.as_str()
.unwrap()
);
Ok(())
}
Create a new file named client.php
and add the following code to it:
<?php
// include auto-loader
include 'vendor/autoload.php';
use GraphQL\Client;
try {
// initialize client with
// endpoint from environment
$sessionPort = getenv('DAGGER_SESSION_PORT') or throw new Exception("DAGGER_SESSION_PORT doesn't exist");
$sessionToken = getenv('DAGGER_SESSION_TOKEN') or throw new Exception("DAGGER_SESSION_TOKEN doesn't exist");
$client = new Client(
'http://127.0.0.1:' . $sessionPort . '/query',
['Authorization' => 'Basic ' . base64_encode($sessionToken . ':')]
);
// define raw GraphQL query
$query = <<<QUERY
query {
container {
from (address: "alpine:latest") {
withExec(args:["uname", "-nrio"]) {
stdout
}
}
}
}
QUERY;
// execute query and print result
$results = $client->runRawQuery($query);
print_r($results->getData()->container->from->withExec->stdout);
} catch (Exception $e) {
print_r($e->getMessage());
exit;
}
This code listing initializes the client library and defines the Dagger pipeline to be executed as a GraphQL query. This query performs the following operations:
- It requests the
from
field of Dagger'sContainer
object type, passing it the address of a container image. To resolve this, Dagger will initialize a container using the specified image and return aContainer
object representing thealpine:latest
container image. - Next, it requests the
withExec
field of theContainer
object from the previous step, passing theuname -a
command to the field as an array of arguments. To resolve this, Dagger will return aContainer
object containing the execution plan. - Finally, it requests the
stdout
field of theContainer
object returned in the previous step. To resolve this, Dagger will execute the command and return aString
containing the results. - The result of the query is a JSON object, which is processed and printed to the output device.
The API endpoint and the HTTP authentication token for the GraphQL client are not statically defined, they must be retrieved at run-time from the special DAGGER_SESSION_PORT
and DAGGER_SESSION_TOKEN
environment variables. This is explained in more detail in the next section.
Step 3: Run the API client
To run the pipeline, the API client needs to communicate with the Dagger Engine, which is responsible for accepting the query, executing it and returning the result. The dagger run
command takes care of initializing a new local instance (or reusing a running instance) of the Dagger Engine on the host system and executing a specified command against it.
The Dagger Engine creates a unique local API endpoint for GraphQL queries for every Dagger session. This API endpoint is served by the local host at the port specified by the DAGGER_SESSION_PORT
environment variable, and can be directly read from the environment in your client code. For example, if DAGGER_SESSION_PORT
is set to 12345
, the API endpoint can be reached at http://127.0.0.1:$DAGGER_SESSION_PORT/query
The Dagger Engine protects the exposed API with an HTTP Basic authentication token which can be retrieved from the DAGGER_SESSION_TOKEN
variable. Treat the DAGGER_SESSION_TOKEN
value as you would any other sensitive credential. Store it securely and avoid passing it to, or over, insecure applications and networks.
Run the API client using the Dagger CLI as follows:
- Rust
- PHP
dagger run cargo run
This command:
- initializes a new Dagger Engine session
- sets the
DAGGER_SESSION_PORT
andDAGGER_SESSION_TOKEN
environment variables. - executes the
cargo run
command in that session
dagger run php client.php
This command:
- initializes a new Dagger Engine session
- sets the
DAGGER_SESSION_PORT
andDAGGER_SESSION_TOKEN
environment variables. - executes the
php client.php
command in that session
The specified command, in turn, invokes the custom API client, connects to the API endpoint specified in the DAGGER_SESSION_PORT
environment variable, sets an HTTP Basic authentication token with DAGGER_SESSION_TOKEN
and executes the GraphQL query. Here is an example of the output:
buildkitsandbox 5.15.0-53-generic unknown Linux
Conclusion
This tutorial explained how to write a custom client for the Dagger GraphQL API. It provided working examples of how to program and run a Dagger pipeline using this client in two different programming languages. A similar approach can be followed in any other programming language with a GraphQL client implementation.
Use the API Reference and the CLI Reference to learn more about the Dagger GraphQL API and the Dagger CLI respectively.