package evcli import ( "bytes" "encoding/json" "fmt" "io" "net/http" "net/url" "github.com/exograd/eventline/pkg/eventline" ) type Client struct { APIKey string ProjectId *eventline.Id httpClient *http.Client baseURI *url.URL } func NewClient(config *APIConfig) (*Client, error) { baseURI, err := url.Parse(config.Endpoint) if err != nil { return nil, fmt.Errorf("invalid api endpoint: %w", err) } client := &Client{ APIKey: config.Key, baseURI: baseURI, httpClient: NewHTTPClient(), } if err != nil { return nil, err } return client, nil } func (c *Client) SendRequest(method string, relURI *url.URL, body, dest interface{}) error { uri := c.baseURI.ResolveReference(relURI) var bodyReader io.Reader if body == nil { bodyReader = nil } else if br, ok := body.(io.Reader); ok { bodyReader = br } else { bodyData, err := json.Marshal(body) if err != nil { return fmt.Errorf("cannot encode body: %w", err) } bodyReader = bytes.NewReader(bodyData) } req, err := http.NewRequest(method, uri.String(), bodyReader) if err != nil { return fmt.Errorf("cannot create request: %w", err) } if c.APIKey != "" { req.Header.Set("Authorization", "Bearer "+c.APIKey) } if c.ProjectId != nil { req.Header.Set("X-Eventline-Project-Id", c.ProjectId.String()) } res, err := c.httpClient.Do(req) if err != nil { return fmt.Errorf("cannot send request: %w", err) } defer res.Body.Close() resBody, err := io.ReadAll(res.Body) if err != nil { return fmt.Errorf("cannot read response body: %w", err) } if res.StatusCode < 200 || res.StatusCode >= 300 { var apiErr APIError err := json.Unmarshal(resBody, &apiErr) if err == nil { return &apiErr } return fmt.Errorf("request failed with status %d: %s", res.StatusCode, string(resBody)) } if dest != nil { if dataPtr, ok := dest.(*[]byte); ok { *dataPtr = resBody } else { if len(resBody) == 0 { return fmt.Errorf("empty response body") } if err := json.Unmarshal(resBody, dest); err != nil { return fmt.Errorf("cannot decode response body: %w", err) } } } return err } func (c *Client) FetchProjects() (eventline.Projects, error) { var projects eventline.Projects cursor := eventline.Cursor{Size: 20} for { var page ProjectPage uri := NewURL("projects") uri.RawQuery = cursor.Query().Encode() err := c.SendRequest("GET", uri, nil, &page) if err != nil { return nil, err } projects = append(projects, page.Elements...) if page.Next == nil { break } cursor = *page.Next } return projects, nil } func (c *Client) FetchProjectById(id eventline.Id) (*eventline.Project, error) { uri := NewURL("projects", "id", id.String()) var project eventline.Project err := c.SendRequest("GET", uri, nil, &project) if err != nil { return nil, err } return &project, nil } func (c *Client) FetchProjectByName(name string) (*eventline.Project, error) { uri := NewURL("projects", "name", name) var project eventline.Project err := c.SendRequest("GET", uri, nil, &project) if err != nil { return nil, err } return &project, nil } func (c *Client) CreateProject(project *eventline.Project) error { uri := NewURL("projects") return c.SendRequest("POST", uri, project, project) } func (c *Client) DeleteProject(id eventline.Id) error { uri := NewURL("projects", "id", id.String()) return c.SendRequest("DELETE", uri, nil, nil) } func (c *Client) UpdateProject(project *eventline.Project) error { uri := NewURL("projects", "id", project.Id.String()) return c.SendRequest("PUT", uri, project, nil) } func (c *Client) CreateIdentity(identity *Identity) error { uri := NewURL("identities") return c.SendRequest("POST", uri, identity, identity) } func (c *Client) FetchIdentities() (Identities, error) { var identities Identities cursor := eventline.Cursor{Size: 20} for { var page IdentityPage uri := NewURL("identities") uri.RawQuery = cursor.Query().Encode() err := c.SendRequest("GET", uri, nil, &page) if err != nil { return nil, err } identities = append(identities, page.Elements...) if page.Next == nil { break } cursor = *page.Next } return identities, nil } func (c *Client) FetchIdentityById(id eventline.Id) (*Identity, error) { uri := NewURL("identities", "id", id.String()) var identity Identity err := c.SendRequest("GET", uri, nil, &identity) if err != nil { return nil, err } return &identity, nil } func (c *Client) UpdateIdentity(identity *Identity) error { uri := NewURL("identities", "id", identity.Id.String()) return c.SendRequest("PUT", uri, identity, identity) } func (c *Client) DeleteIdentity(id eventline.Id) error { uri := NewURL("identities", "id", id.String()) return c.SendRequest("DELETE", uri, nil, nil) } func (c *Client) ReplayEvent(id string) (*eventline.Event, error) { var event eventline.Event uri := NewURL("events", "id", id, "replay") err := c.SendRequest("POST", uri, nil, &event) if err != nil { return nil, err } return &event, nil } func (c *Client) FetchJobByName(name string) (*eventline.Job, error) { uri := NewURL("jobs", "name", name) var job eventline.Job err := c.SendRequest("GET", uri, nil, &job) if err != nil { return nil, err } return &job, nil } func (c *Client) FetchJobs() (eventline.Jobs, error) { var jobs eventline.Jobs cursor := eventline.Cursor{Size: 20} for { var page JobPage uri := NewURL("jobs") uri.RawQuery = cursor.Query().Encode() err := c.SendRequest("GET", uri, nil, &page) if err != nil { return nil, err } jobs = append(jobs, page.Elements...) if page.Next == nil { break } cursor = *page.Next } return jobs, nil } func (c *Client) DeployJob(spec *eventline.JobSpec, dryRun bool) (*eventline.Job, error) { uri := NewURL("jobs", "name", spec.Name) query := url.Values{} if dryRun { query.Add("dry-run", "") } uri.RawQuery = query.Encode() if dryRun { if err := c.SendRequest("PUT", uri, spec, nil); err != nil { return nil, err } return nil, nil } else { var job eventline.Job if err := c.SendRequest("PUT", uri, spec, &job); err != nil { return nil, err } return &job, nil } } func (c *Client) DeployJobs(specs []*eventline.JobSpec, dryRun bool) ([]*eventline.Job, error) { uri := NewURL("jobs") query := url.Values{} if dryRun { query.Add("dry-run", "") } uri.RawQuery = query.Encode() if dryRun { if err := c.SendRequest("PUT", uri, specs, nil); err != nil { return nil, err } return nil, nil } else { var jobs []*eventline.Job if err := c.SendRequest("PUT", uri, specs, &jobs); err != nil { return nil, err } return jobs, nil } } func (c *Client) DeleteJob(id string) error { uri := NewURL("jobs", "id", id) return c.SendRequest("DELETE", uri, nil, nil) } func (c *Client) ExecuteJob(id string, input *eventline.JobExecutionInput) (*eventline.JobExecution, error) { uri := NewURL("jobs", "id", id, "execute") var jobExecution eventline.JobExecution if err := c.SendRequest("POST", uri, input, &jobExecution); err != nil { return nil, err } return &jobExecution, nil } func (c *Client) FetchJobExecution(id eventline.Id) (*eventline.JobExecution, error) { uri := NewURL("job_executions", "id", id.String()) var je eventline.JobExecution err := c.SendRequest("GET", uri, nil, &je) if err != nil { return nil, err } return &je, nil } func (c *Client) AbortJobExecution(id eventline.Id) error { uri := NewURL("job_executions", "id", id.String(), "abort") return c.SendRequest("POST", uri, nil, nil) } func (c *Client) RestartJobExecution(id eventline.Id) error { uri := NewURL("job_executions", "id", id.String(), "restart") return c.SendRequest("POST", uri, nil, nil) }