README
ΒΆ
digitalhub-cli-sdk
A Go SDK for interacting programmatically with DigitalHub Core API.
It provides programmatic access to the same capabilities offered by the DigitalHub CLI (dhcli), but is completely standalone and can be imported into any Go application or microservice.
β¨ Highlights
- Official Core API v1 support
- CRUD operations on all core resources (projects, artifacts, functions, runs, tasks, etc.)
- Function execution (
RunService) - Stop / Resume for runnable resources
- Logs and Metrics retrieval (same semantics as
dhcli) - S3-compatible transfer (Upload/Download via MinIO / AWS S3)
- Task auto-creation for Run (e.g.
python+jobβpython+job:run) - Backwards compatible with
dhclilogic - No CLI dependency
- Usable standalone inside external Go applications
π¦ Installation
go get github.com/scc-digitalhub/digitalhub-cli-sdk
π§ Configuration
The SDK uses the same configuration model as the DigitalHub CLI. Configuration can be provided via environment variables (recommended for external programs).
package main
import (
"os"
"github.com/scc-digitalhub/digitalhub-cli-sdk/sdk/config"
)
func main() {
cfg := config.Config{
Core: config.CoreConfig{
BaseURL: os.Getenv("DHCORE_ENDPOINT"),
APIVersion: os.Getenv("DHCORE_API_VERSION"),
AccessToken: os.Getenv("DHCORE_ACCESS_TOKEN"),
},
}
_ = cfg
}
Required environment variables
| Variable | Example |
|---|---|
| DHCORE_ENDPOINT | https://core.dev.atlas.fbk.eu |
| DHCORE_API_VERSION | v1 |
| DHCORE_ACCESS_TOKEN | eyJhbGciOi... |
Optional S3/MinIO variables (TransferService)
These are used when you call transfer.Upload(...) / transfer.Download(...).
| Variable | Example |
|---|---|
| AWS_ACCESS_KEY_ID | A68K... |
| AWS_SECRET_ACCESS_KEY | ... |
| AWS_SESSION_TOKEN | ... |
| AWS_REGION | us-east-1 |
| AWS_ENDPOINT_URL | https://minio-api.dev.atlas.fbk.eu |
| S3_BUCKET | datalake |
Note: The SDK config struct uses
config.S3Config{...}; you can map env vars however you prefer in your app.
π Usage Examples
In the examples below,
endpointis the Core resource endpoint (e.g."artifacts","functions","runs"). If you start from CLI-like resource names, you can reuse the same mapping used by the CLI:endpoint := utils.TranslateEndpoint(resource) // e.g. "runs" -> "run"
CRUD: list resources (all pages)
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/scc-digitalhub/digitalhub-cli-sdk/sdk/config"
"github.com/scc-digitalhub/digitalhub-cli-sdk/sdk/services/crud"
)
func main() {
cfg := config.Config{
Core: config.CoreConfig{
BaseURL: os.Getenv("DHCORE_ENDPOINT"),
APIVersion: os.Getenv("DHCORE_API_VERSION"),
AccessToken: os.Getenv("DHCORE_ACCESS_TOKEN"),
},
}
ctx := context.Background()
svc, err := crud.NewCrudService(ctx, cfg)
if err != nil {
panic(err)
}
items, _, err := svc.ListAllPages(ctx, crud.ListRequest{
ResourceRequest: crud.ResourceRequest{
Project: "project-name",
Resource: "artifacts",
},
Params: map[string]string{
"size": "200",
"sort": "updated,asc",
},
})
if err != nil {
panic(err)
}
out, _ := json.MarshalIndent(items, "", " ")
fmt.Println(string(out))
}
CRUD: get a resource
Get by ID
body, _, err := svc.Get(ctx, crud.GetRequest{
ResourceRequest: crud.ResourceRequest{
Project: "project-name",
Resource: "artifacts",
},
ID: "artifact-id",
})
_ = body
_ = err
Get latest version by name (CLI-compatible semantics)
body, _, err := svc.Get(ctx, crud.GetRequest{
ResourceRequest: crud.ResourceRequest{
Project: "project-name",
Resource: "artifacts",
},
// When ID is empty, Get uses:
// ?name=<name>&versions=latest
Name: "my-artifact",
})
_ = body
_ = err
CRUD: create a resource (from file or name)
package main
import (
"context"
"os"
"github.com/scc-digitalhub/digitalhub-cli-sdk/sdk/config"
"github.com/scc-digitalhub/digitalhub-cli-sdk/sdk/services/crud"
)
func main() {
cfg := config.Config{
Core: config.CoreConfig{
BaseURL: os.Getenv("DHCORE_ENDPOINT"),
APIVersion: os.Getenv("DHCORE_API_VERSION"),
AccessToken: os.Getenv("DHCORE_ACCESS_TOKEN"),
},
}
ctx := context.Background()
svc, err := crud.NewCrudService(ctx, cfg)
if err != nil {
panic(err)
}
err = svc.Create(ctx, crud.CreateRequest{
ResourceRequest: crud.ResourceRequest{
Project: "project-name",
Resource: "artifacts",
},
// One of:
// - FilePath: "/path/to/resource.yaml"
// - Name: "my-resource"
FilePath: "/path/to/artifact.yaml",
ResetID: false,
})
if err != nil {
panic(err)
}
}
CRUD: update a resource (raw JSON body)
body := []byte(`{"project":"project-name","kind":"...","spec":{"foo":"bar"}}`)
err := svc.Update(ctx, crud.UpdateRequest{
ResourceRequest: crud.ResourceRequest{
Project: "project-name",
Resource: "artifacts",
},
ID: "artifact-id",
Body: body,
})
if err != nil {
panic(err)
}
CRUD: delete a resource (by ID or by name)
// Delete by ID
err := svc.Delete(ctx, crud.DeleteRequest{
ResourceRequest: crud.ResourceRequest{
Project: "project-name",
Resource: "artifacts",
},
ID: "artifact-id",
Cascade: false,
})
if err != nil {
panic(err)
}
// Delete all versions by name
err = svc.Delete(ctx, crud.DeleteRequest{
ResourceRequest: crud.ResourceRequest{
Project: "project-name",
Resource: "artifacts",
},
Name: "my-artifact",
Cascade: false,
})
if err != nil {
panic(err)
}
βΆοΈ Run / Stop / Resume (RunService)
The run service replicates the CLI behavior:
- resolves functions by name or ID
- finds or creates tasks
- creates runs with the correct
kind(e.g.python+jobβpython+job:run)
package main
import (
"context"
"os"
"github.com/scc-digitalhub/digitalhub-cli-sdk/sdk/config"
"github.com/scc-digitalhub/digitalhub-cli-sdk/sdk/services/run"
)
func main() {
cfg := config.Config{
Core: config.CoreConfig{
BaseURL: os.Getenv("DHCORE_ENDPOINT"),
APIVersion: os.Getenv("DHCORE_API_VERSION"),
AccessToken: os.Getenv("DHCORE_ACCESS_TOKEN"),
},
}
ctx := context.Background()
runSvc, err := run.NewRunService(ctx, cfg)
if err != nil {
panic(err)
}
// Create a run (kind is normalized to "<taskKind>:run")
req := run.RunRequest{
Project: "project-name",
TaskKind: "python+job", // normalized to "python+job:run"
FunctionName: "test-run", // or FunctionID
// FunctionID: "....",
InputSpec: map[string]any{
"input": 123,
},
}
if err := runSvc.Run(ctx, req); err != nil {
panic(err)
}
// Stop / Resume a runnable resource (e.g. runs)
_, _, _ = runSvc.Stop(ctx, run.StopRequest{
RunResourceRequest: run.RunResourceRequest{
Project: "project-name",
Resource: "runs",
ID: "run-id",
},
})
_, _, _ = runSvc.Resume(ctx, run.ResumeRequest{
RunResourceRequest: run.RunResourceRequest{
Project: "project-name",
Resource: "runs",
ID: "run-id",
},
})
}
The SDK automatically generates the correct run kind:
{
"kind": "python+job:run"
}
π Logs (CLI-compatible semantics)
Logs are retrieved via the same /logs API used by the CLI, and the default container name is inferred from spec.task if you donβt provide one.
package main
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/scc-digitalhub/digitalhub-cli-sdk/sdk/config"
"github.com/scc-digitalhub/digitalhub-cli-sdk/sdk/services/run"
)
func main() {
cfg := config.Config{
Core: config.CoreConfig{
BaseURL: os.Getenv("DHCORE_ENDPOINT"),
APIVersion: os.Getenv("DHCORE_API_VERSION"),
AccessToken: os.Getenv("DHCORE_ACCESS_TOKEN"),
},
}
ctx := context.Background()
svc, err := run.NewRunService(ctx, cfg)
if err != nil {
panic(err)
}
// 1) Get logs list
logBody, _, err := svc.GetLogs(ctx, run.LogRequest{
RunResourceRequest: run.RunResourceRequest{
Project: "project-name",
Resource: "runs",
ID: "run-id",
},
})
if err != nil {
panic(err)
}
// 2) Parse logs and print the main container (same as CLI)
var logs []interface{}
if err := json.Unmarshal(logBody, &logs); err != nil {
panic(err)
}
// Compute default container like the CLI:
// - GET resource to read spec.task
// - container name is: c-<taskFormatted>-<id>
resBody, _, err := svc.GetResource(ctx, run.LogRequest{
RunResourceRequest: run.RunResourceRequest{
Project: "project-name",
Resource: "runs",
ID: "run-id",
},
})
if err != nil {
panic(err)
}
var m map[string]interface{}
_ = json.Unmarshal(resBody, &m)
spec := m["spec"].(map[string]interface{})
task := spec["task"].(string)
taskFormatted := strings.ReplaceAll(task[:strings.Index(task, ":")], "+", "")
mainContainer := fmt.Sprintf("c-%s-%s", taskFormatted, "run-id")
// Find matching entry and print base64 decoded content
for _, e := range logs {
em := e.(map[string]interface{})
st := em["status"].(map[string]interface{})
if st["container"] == mainContainer {
contentB64 := em["content"].(string)
raw, _ := base64.StdEncoding.DecodeString(contentB64)
fmt.Println(string(raw))
break
}
}
}
Tip: if you already know the container name, you can directly filter the logs list by
status.container.
π Metrics (CLI-compatible semantics)
Metrics are read from the selected container log entry (status.metrics) exactly like the CLI.
package main
import (
"context"
"os"
"github.com/scc-digitalhub/digitalhub-cli-sdk/sdk/config"
"github.com/scc-digitalhub/digitalhub-cli-sdk/sdk/services/run"
)
func main() {
cfg := config.Config{
Core: config.CoreConfig{
BaseURL: os.Getenv("DHCORE_ENDPOINT"),
APIVersion: os.Getenv("DHCORE_API_VERSION"),
AccessToken: os.Getenv("DHCORE_ACCESS_TOKEN"),
},
}
ctx := context.Background()
svc, err := run.NewRunService(ctx, cfg)
if err != nil {
panic(err)
}
// Prints pretty JSON or "No metrics for this run."
if err := svc.PrintMetrics(ctx, run.MetricsRequest{
RunResourceRequest: run.RunResourceRequest{
Project: "project-name",
Resource: "runs",
ID: "run-id",
},
// Container: "" // optional; if empty, infer main container like CLI
}); err != nil {
panic(err)
}
}
β¬οΈβ¬οΈ Upload / Download (S3 / MinIO)
The transfer service uses:
- Core API to discover/download/upload resources
- S3-compatible object storage for the actual file transfer
package main
import (
"context"
"os"
"github.com/scc-digitalhub/digitalhub-cli-sdk/sdk/config"
"github.com/scc-digitalhub/digitalhub-cli-sdk/sdk/services/transfer"
)
func main() {
cfg := config.Config{
Core: config.CoreConfig{
BaseURL: os.Getenv("DHCORE_ENDPOINT"),
APIVersion: os.Getenv("DHCORE_API_VERSION"),
AccessToken: os.Getenv("DHCORE_ACCESS_TOKEN"),
},
S3: config.S3Config{
AccessKey: os.Getenv("AWS_ACCESS_KEY_ID"),
SecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
AccessToken: os.Getenv("AWS_SESSION_TOKEN"),
Region: os.Getenv("AWS_REGION"),
EndpointURL: os.Getenv("AWS_ENDPOINT_URL"),
},
}
ctx := context.Background()
tr, err := transfer.NewTransferService(ctx, cfg)
if err != nil {
panic(err)
}
// Upload (bucket is optional; your CLI uses "datalake" as default)
_, err = tr.Upload(ctx, "artifacts", transfer.UploadRequest{
Project: "project-name",
Resource: "artifacts",
Name: "my-artifact",
Input: "/tmp/data.bin",
Verbose: true,
Bucket: os.Getenv("S3_BUCKET"),
})
if err != nil {
panic(err)
}
// Download
_, err = tr.Download(ctx, "artifacts", transfer.DownloadRequest{
Project: "project-name",
Resource: "artifacts",
Name: "my-artifact",
Destination: "/tmp/out",
Verbose: true,
})
if err != nil {
panic(err)
}
}
π§ͺ Running integration tests
export DHCORE_ENDPOINT=...
export DHCORE_API_VERSION=v1
export DHCORE_ACCESS_TOKEN=...
go test ./...
π Repository layout
sdk/
config/
services/
crud/
run/
transfer/
utils/
The SDK is fully self-contained under /sdk and can be imported independently of the CLI.
πͺͺ License
Apache-2.0