Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

irods_client_http_go

A Go client library for the iRODS HTTP API.

Designed after the Python (irods_client_http_python) and Java (irods_client_http_java) reference implementations with Go-idiomatic conventions:

  • context.Context support on every call
  • Multiple return values (value, error) instead of exceptions
  • Streaming I/O (io.Reader / io.WriterAt) for large file transfers
  • Goroutine-safe client with connection pooling
  • No external dependencies — standard library only

Requirements

  • Go 1.21 or later

Installation

go get github.com/irods/irods_client_http_go

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/irods/irods_client_http_go/irods"
)

func main() {
    ctx := context.Background()

    client := irods.New("https://irods-http.example.org:9000")
    if err := client.Authenticate(ctx, "alice", "password"); err != nil {
        log.Fatal(err)
    }

    resp, err := client.DataObject().Stat(ctx, "/zone/home/alice/data.txt", nil)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(resp.Body)
}

Usage

Authentication

client := irods.New("https://irods-http.example.org:9000")

// Username / password (HTTP Basic -> Bearer token)
err := client.Authenticate(ctx, "alice", "password")

// Pre-existing token (e.g. OpenID Connect)
client := irods.NewWithToken("https://irods-http.example.org:9000", "existing-token")

// Replace the token at any time (goroutine-safe)
client.SetToken("new-token")

Client Options

client := irods.New(
    "https://irods-http.example.org:9000",
    irods.WithDialTimeout(10 * time.Second),
    irods.WithResponseHeaderTimeout(30 * time.Second),
    irods.WithMaxIdleConnsPerHost(20),         // increase for parallel workloads
    irods.WithDisableCompression(true),        // for pre-compressed data
)

Per-operation timeouts are set via context.WithTimeout at the call site:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
resp, err := client.DataObject().Read(ctx, "/zone/home/alice/large.bin", nil)

Data Objects

do := client.DataObject()

// Stat
resp, err := do.Stat(ctx, "/zone/home/alice/data.txt", nil)

// Read small object into memory
resp, err := do.Read(ctx, "/zone/home/alice/data.txt", nil)
fmt.Println(resp.Body)

// Stream a large object (caller must close Body)
stream, err := do.ReadStream(ctx, "/zone/home/alice/large.bin", nil)
if err != nil { log.Fatal(err) }
defer stream.Body.Close()
io.Copy(os.Stdout, stream.Body)

// Write small object
err = func() error {
    _, err := do.Write(ctx, "/zone/home/alice/hello.txt", []byte("hello"), nil)
    return err
}()

// Stream-write a large object
f, _ := os.Open("/local/large.bin")
defer f.Close()
_, err = do.WriteStream(ctx, "/zone/home/alice/large.bin", f, nil)

// Upload local file using parallel streams
_, err = do.UploadFile(ctx, "/zone/home/alice/large.bin", "/local/large.bin", 4, nil)

// Download to local file using parallel streams
_, err = do.DownloadFile(ctx, "/zone/home/alice/large.bin", "/local/copy.bin", 4, nil)

// Touch (create or update mtime)
_, err = do.Touch(ctx, "/zone/home/alice/new.txt", nil)

// Remove
_, err = do.Remove(ctx, "/zone/home/alice/old.txt", 0, nil)

// Rename / Move
_, err = do.Rename(ctx, "/zone/home/alice/old.txt", "/zone/home/alice/new.txt")

// Copy
_, err = do.Copy(ctx, "/zone/home/alice/src.txt", "/zone/home/alice/dst.txt", nil)

// Replicate to another resource
_, err = do.Replicate(ctx, "/zone/home/alice/data.txt", "", "demoResc2", nil)

// Checksum
_, err = do.CalculateChecksum(ctx, "/zone/home/alice/data.txt", nil)
_, err = do.VerifyChecksum(ctx, "/zone/home/alice/data.txt", nil)

// Metadata (AVU)
_, err = do.ModifyMetadata(ctx, "/zone/home/alice/data.txt", []irods.ModifyMetadataOperations{
    {Operation: irods.MetadataOperationAdd, Attribute: "project", Value: "wilma"},
    {Operation: irods.MetadataOperationAdd, Attribute: "year",    Value: "2026", Units: "CE"},
}, nil)

// Permissions
_, err = do.SetPermission(ctx, "/zone/home/alice/data.txt", "bob", irods.PermissionRead, nil)
_, err = do.ModifyPermissions(ctx, "/zone/home/alice/data.txt", []irods.ModifyPermissionsOperations{
    {EntityName: "bob",   ACL: irods.PermissionRead},
    {EntityName: "carol", ACL: irods.PermissionOwn},
}, nil)

Parallel Write (low-level)

// Manual parallel write — use UploadFile for the common case.
initResp, err := do.ParallelWriteInit(ctx, "/zone/home/alice/large.bin", 4, nil)
// parse handle from initResp.Body ...
_, err = do.ParallelWriteShutdown(ctx, handle)

// High-level helper: manages init / fan-out / shutdown automatically
f, _ := os.Open("/local/large.bin")
defer f.Close()
_, err = do.ParallelWrite(ctx, "/zone/home/alice/large.bin", f, 4, nil)

Parallel Read

// ParallelRead writes each chunk directly to the correct file offset via io.WriterAt —
// no reordering buffer needed. *os.File implements io.WriterAt.
out, _ := os.Create("/local/copy.bin")
defer out.Close()
_, err = do.ParallelRead(ctx, "/zone/home/alice/large.bin", out, 4, nil)

Collections

col := client.Collections()

// Create (with intermediate paths)
_, err := col.Create(ctx, "/zone/home/alice/a/b/c", irods.IntPtr(1))

// List
resp, err := col.List(ctx, "/zone/home/alice", nil)

// Stat
resp, err := col.Stat(ctx, "/zone/home/alice", nil)

// Remove recursively
_, err = col.Remove(ctx, "/zone/home/alice/tmp", &irods.CollectionsRemoveParams{
    Recurse: irods.IntPtr(1),
})

// Rename / Move
_, err = col.Rename(ctx, "/zone/home/alice/old", "/zone/home/alice/new")

// Permission inheritance
_, err = col.SetInheritance(ctx, "/zone/home/alice/shared", 1, nil)

// Metadata
_, err = col.ModifyMetadata(ctx, "/zone/home/alice", []irods.ModifyMetadataOperations{
    {Operation: irods.MetadataOperationAdd, Attribute: "tag", Value: "important"},
}, nil)

Users and Groups

ug := client.UserGroupOperations()

// Create user
_, err := ug.CreateUser(ctx, "bob", "zone", nil)

// Set password
_, err = ug.SetPassword(ctx, "bob", "zone", "s3cr3t")

// Create group and add member
_, err = ug.CreateGroup(ctx, "scientists")
_, err = ug.AddToGroup(ctx, "bob", "zone", "scientists")

// Check membership
resp, err := ug.IsMemberOfGroup(ctx, "scientists", "bob", "zone")

// List
resp, err = ug.Users(ctx)
resp, err = ug.Groups(ctx)

Resources

resc := client.ResourceOperations()

// Create a unixfilesystem resource
_, err := resc.Create(ctx, "myResc", "unixfilesystem", &irods.ResourceCreateParams{
    Host:      irods.StringPtr("icat.example.org"),
    VaultPath: irods.StringPtr("/srv/irods/myResc"),
})

// Build a hierarchy
_, err = resc.AddChild(ctx, "parentResc", "myResc", nil)

// Modify a property
_, err = resc.Modify(ctx, "myResc", irods.ResourcePropertyStatus, "up")

// Rebalance
_, err = resc.Rebalance(ctx, "parentResc")

// Stat
resp, err := resc.Stat(ctx, "myResc")

Zones

zones := client.ZoneOperations()

// Add remote zone
_, err := zones.Add(ctx, "remoteZone", &irods.ZoneAddParams{
    ConnectionInfo: irods.StringPtr("remote.example.org:1247"),
})

// Report and stat
resp, err := zones.Report(ctx)
resp, err  = zones.Stat(ctx, "remoteZone")

Tickets

tk := client.TicketOperations()

// Create a read ticket limited to 10 uses
resp, err := tk.Create(ctx, "/zone/home/alice/data.txt", &irods.TicketCreateParams{
    Type:     irods.StringPtr("read"),
    UseCount: irods.IntPtr(10),
})

// Remove
_, err = tk.Remove(ctx, "ticketName")

Queries

q := client.QueryOperations()

// GenQuery
resp, err := q.ExecuteGenQuery(ctx,
    "SELECT COLL_NAME, DATA_NAME WHERE COLL_NAME = '/zone/home/alice'",
    &irods.QueryExecuteGenQueryParams{Count: irods.IntPtr(100)},
)

// Specific query
resp, err = q.ExecuteSpecificQuery(ctx, "ShowCollAcls", &irods.QueryExecuteSpecificQueryParams{
    Args: irods.StringPtr("/zone/home/alice"),
})

Rules

rules := client.RuleOperations()

// List available rule engine plugins
resp, err := rules.ListRuleEngines(ctx)

// Execute a rule
_, err = rules.Execute(ctx, `myRule { writeLine("serverLog", "hello"); }`, nil)

// Remove a delay rule
_, err = rules.RemoveDelayRule(ctx, 42)

Server Information

// No authentication required
resp, err := client.Information().Get(ctx)
fmt.Println(resp.Body)

File Handle (FUSE / random access)

IRODSFileHandle provides an open/read/write/close lifecycle for use cases such as FUSE. Each ReadAt and WriteAt maps to one HTTP request at the specified offset. Contiguous small writes are coalesced in an internal buffer to reduce HTTP overhead.

do := client.DataObject()

// Open for read/write with a 4 MB write buffer
h, err := do.OpenFile(ctx, "/zone/home/alice/data.bin", irods.OpenFlagReadWrite, 4*1024*1024)
if err != nil { log.Fatal(err) }
defer h.Close(ctx)

// Random read
buf := make([]byte, 512)
n, err := h.ReadAt(ctx, buf, 1024)

// Random write (buffered)
_, err = h.WriteAt(ctx, []byte("patch"), 2048)

// Explicit flush (e.g. on FUSE fsync)
err = h.Flush(ctx)

Error Handling

Three distinct error types allow callers to handle failures at the right granularity:

resp, err := client.DataObject().Stat(ctx, "/zone/home/alice/data.txt", nil)
if err != nil {
    var paramErr *irods.ErrInvalidParam
    var httpErr  *irods.ErrHTTP
    var irodsErr *irods.ErrIRODS

    switch {
    case errors.As(err, &paramErr):
        // bad argument before the request was sent
        fmt.Println("invalid parameter:", paramErr.Param, paramErr.Message)
    case errors.As(err, &httpErr):
        // 4xx / 5xx HTTP response
        fmt.Println("HTTP error:", httpErr.StatusCode)
    case errors.As(err, &irodsErr):
        // HTTP 200 but iRODS reported a non-zero status code
        fmt.Println("iRODS error:", irodsErr.StatusCode, irodsErr.StatusMessage)
    default:
        fmt.Println("network or context error:", err)
    }
}

Pointer Helpers

Optional parameter fields use pointer types. Use the provided helpers to avoid temporary variables:

irods.StringPtr("demoResc")   // *string
irods.IntPtr(1)               // *int
irods.Int64Ptr(4096)          // *int64

License

Copyright (c) 2010-2026, The Arizona Board of Regents on behalf of The University of Arizona. See LICENSE for details.

About

Go wrapper for iRODS HTTP API

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages