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 }