chore: 初始化 Slurm Go SDK 项目骨架和核心客户端

添加 Go 模块定义、.gitignore、包文档、HTTP Client 核心、Token 认证和错误处理。

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
dailz
2026-04-08 18:28:32 +08:00
commit 73453ddd10
6 changed files with 254 additions and 0 deletions

153
internal/slurm/client.go Normal file
View File

@@ -0,0 +1,153 @@
package slurm
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
const (
// DefaultBaseURL is the default Slurm REST API endpoint.
DefaultBaseURL = "http://localhost:6820/"
// DefaultUserAgent is the default User-Agent header value.
DefaultUserAgent = "slurm-go-sdk"
)
// Client manages communication with the Slurm REST API.
type Client struct {
client *http.Client
baseURL *url.URL
UserAgent string
common service
Jobs *JobsService
Nodes *NodesService
Partitions *PartitionsService
Reservations *ReservationsService
Diag *DiagService
Ping *PingService
Licenses *LicensesService
Reconfigure *ReconfigureService
Shares *SharesService
}
// service is the base struct for all API services.
type service struct {
client *Client
}
type JobsService service
type NodesService service
type PartitionsService service
type ReservationsService service
type DiagService service
type PingService service
type LicensesService service
type ReconfigureService service
type SharesService service
// Response wraps an http.Response and will later hold Meta/Errors/Warnings
// fields parsed from Slurm API responses.
type Response struct {
*http.Response
}
// NewClient returns a new Slurm API client. If httpClient is nil,
// http.DefaultClient is used.
func NewClient(baseURL string, httpClient *http.Client) (*Client, error) {
if httpClient == nil {
httpClient = http.DefaultClient
}
parsedURL, err := url.Parse(baseURL)
if err != nil {
return nil, fmt.Errorf("invalid base URL %q: %w", baseURL, err)
}
if !strings.HasSuffix(parsedURL.Path, "/") {
parsedURL.Path += "/"
}
c := &Client{
client: httpClient,
baseURL: parsedURL,
UserAgent: DefaultUserAgent,
}
c.common.client = c
c.Jobs = (*JobsService)(&c.common)
c.Nodes = (*NodesService)(&c.common)
c.Partitions = (*PartitionsService)(&c.common)
c.Reservations = (*ReservationsService)(&c.common)
c.Diag = (*DiagService)(&c.common)
c.Ping = (*PingService)(&c.common)
c.Licenses = (*LicensesService)(&c.common)
c.Reconfigure = (*ReconfigureService)(&c.common)
c.Shares = (*SharesService)(&c.common)
return c, nil
}
// NewRequest creates an API request. A relative URL path can be provided in
// urlStr, which will be resolved against the base URL of the Client.
// If body is not nil, it will be JSON-encoded and set as the request body.
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
u, err := c.baseURL.Parse(urlStr)
if err != nil {
return nil, fmt.Errorf("invalid relative URL %q: %w", urlStr, err)
}
var buf io.Reader
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("failed to marshal request body: %w", err)
}
buf = bytes.NewReader(b)
}
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
if c.UserAgent != "" {
req.Header.Set("User-Agent", c.UserAgent)
}
return req, nil
}
// Do sends an API request and returns the API response. The API response is
// JSON-decoded into the value pointed to by v (if v is not nil).
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
req = req.WithContext(ctx)
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if err := CheckResponse(resp); err != nil {
return nil, err
}
response := &Response{Response: resp}
if v != nil {
if err := json.NewDecoder(resp.Body).Decode(v); err != nil {
return response, fmt.Errorf("failed to decode response: %w", err)
}
}
return response, nil
}