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:
39
internal/slurm/auth.go
Normal file
39
internal/slurm/auth.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package slurm
|
||||
|
||||
import "net/http"
|
||||
|
||||
// TokenAuthTransport implements http.RoundTripper and injects
|
||||
// X-SLURM-USER-NAME and X-SLURM-USER-TOKEN headers into every request.
|
||||
type TokenAuthTransport struct {
|
||||
UserName string
|
||||
Token string
|
||||
|
||||
// Base is the underlying RoundTripper. If nil, http.DefaultTransport is used.
|
||||
Base http.RoundTripper
|
||||
}
|
||||
|
||||
// RoundTrip executes a single HTTP request, adding Slurm authentication headers.
|
||||
func (t *TokenAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req2 := cloneRequest(req)
|
||||
req2.Header.Set("X-SLURM-USER-NAME", t.UserName)
|
||||
req2.Header.Set("X-SLURM-USER-TOKEN", t.Token)
|
||||
return t.transport().RoundTrip(req2)
|
||||
}
|
||||
|
||||
// Client returns a new http.Client that uses this TokenAuthTransport.
|
||||
func (t *TokenAuthTransport) Client() *http.Client {
|
||||
return &http.Client{Transport: t}
|
||||
}
|
||||
|
||||
func (t *TokenAuthTransport) transport() http.RoundTripper {
|
||||
if t.Base != nil {
|
||||
return t.Base
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
// cloneRequest creates a shallow copy of the request with a deep copy of the headers.
|
||||
func cloneRequest(req *http.Request) *http.Request {
|
||||
r := req.Clone(req.Context())
|
||||
return r
|
||||
}
|
||||
153
internal/slurm/client.go
Normal file
153
internal/slurm/client.go
Normal 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
|
||||
}
|
||||
38
internal/slurm/errors.go
Normal file
38
internal/slurm/errors.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package slurm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ErrorResponse represents an error returned by the Slurm REST API.
|
||||
type ErrorResponse struct {
|
||||
Response *http.Response
|
||||
Message string
|
||||
}
|
||||
|
||||
func (r *ErrorResponse) Error() string {
|
||||
return fmt.Sprintf("%v %v: %d %s",
|
||||
r.Response.Request.Method, r.Response.Request.URL,
|
||||
r.Response.StatusCode, r.Message)
|
||||
}
|
||||
|
||||
// CheckResponse checks the API response for errors. It returns nil if the
|
||||
// response is a 2xx status code. For non-2xx codes, it reads the response
|
||||
// body and returns an ErrorResponse.
|
||||
func CheckResponse(r *http.Response) error {
|
||||
if c := r.StatusCode; c >= 200 && c <= 299 {
|
||||
return nil
|
||||
}
|
||||
|
||||
errorResponse := &ErrorResponse{Response: r}
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil || len(data) == 0 {
|
||||
errorResponse.Message = r.Status
|
||||
return errorResponse
|
||||
}
|
||||
|
||||
errorResponse.Message = string(data)
|
||||
return errorResponse
|
||||
}
|
||||
18
internal/slurm/slurm.go
Normal file
18
internal/slurm/slurm.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Package slurm provides a Go client for the Slurm REST API (v0.0.40).
|
||||
//
|
||||
// The client handles authentication via X-SLURM-USER-NAME and X-SLURM-USER-TOKEN
|
||||
// headers, request/response marshaling, and error handling.
|
||||
//
|
||||
// Basic usage:
|
||||
//
|
||||
// httpClient := &http.Client{
|
||||
// Transport: &slurm.TokenAuthTransport{
|
||||
// UserName: "slurm",
|
||||
// Token: "your-token",
|
||||
// },
|
||||
// }
|
||||
// client, err := slurm.NewClient("http://localhost:6820", httpClient)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
package slurm
|
||||
Reference in New Issue
Block a user