gitmote / internal / scaleway / jobs.go · history · branches
2394 bytes · raw
// Package scaleway is a thin client for Scaleway Serverless Jobs — the CI runner
// substrate (tasks/16-ci.md §Runner substrate). A job definition is created once
// out of band; the app injects per-run env at start, so one definition serves
// all repos. The client is fire-and-forget: the caller triggers a run and never
// waits on it — the runner reports back over the internal API (stage 4).
package scaleway
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
// triggerTimeout bounds the single start POST; the job itself runs
// asynchronously on Scaleway well beyond this.
const triggerTimeout = 10 * time.Second
// JobsClient starts runs of one Scaleway Serverless Job definition.
type JobsClient struct {
secretKey string
region string
jobDefinitionID string
httpClient *http.Client
}
// NewJobsClient returns a client for the given job definition. When
// jobDefinitionID is empty (local dev / tests), Trigger is a no-op.
func NewJobsClient(secretKey, region, jobDefinitionID string) *JobsClient {
return &JobsClient{
secretKey: secretKey,
region: region,
jobDefinitionID: jobDefinitionID,
httpClient: &http.Client{Timeout: triggerTimeout},
}
}
// Trigger starts one job run with env injected as its environment variables. It
// is a no-op returning nil when the client has no job definition configured. A
// response status >= 300 is an error, with the response body attached.
func (c *JobsClient) Trigger(ctx context.Context, env map[string]string) error {
if c.jobDefinitionID == "" {
return nil
}
url := fmt.Sprintf(
"https://api.scaleway.com/serverless-jobs/v1alpha2/regions/%s/job-definitions/%s/start",
c.region, c.jobDefinitionID,
)
body, err := json.Marshal(map[string]any{"environment_variables": env})
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Auth-Token", c.secretKey)
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode >= 300 {
msg, _ := io.ReadAll(resp.Body)
return fmt.Errorf("scaleway: start job definition %s returned %d: %s",
c.jobDefinitionID, resp.StatusCode, msg)
}
return nil
}