Imported and modified evcli's api client code
This commit is contained in:
parent
64ac1f44e0
commit
6844355a92
11 changed files with 523 additions and 5 deletions
13
external/evcli/LICENSE
vendored
Normal file
13
external/evcli/LICENSE
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
Copyright (c) 2022 Exograd SAS.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
3
external/evcli/README.md
vendored
Normal file
3
external/evcli/README.md
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# evcli
|
||||
|
||||
The code in this directory comes from [evcli](https://github.com/exograd/eventline/tree/master/cmd/evcli). It is the property of [Exograd](https://www.exograd.com/) under the [ISC License](https://github.com/exograd/eventline/blob/master/LICENSE).
|
86
external/evcli/api.go
vendored
Normal file
86
external/evcli/api.go
vendored
Normal file
|
@ -0,0 +1,86 @@
|
|||
package evcli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/exograd/eventline/pkg/eventline"
|
||||
"github.com/exograd/go-daemon/check"
|
||||
)
|
||||
|
||||
type APIError struct {
|
||||
Message string `json:"error"`
|
||||
Code string `json:"code,omitempty"`
|
||||
RawData json.RawMessage `json:"data,omitempty"`
|
||||
Data interface{} `json:"-"`
|
||||
}
|
||||
|
||||
type InvalidRequestBodyError struct {
|
||||
ValidationErrors check.ValidationErrors `json:"validation_errors"`
|
||||
}
|
||||
|
||||
func (err APIError) Error() string {
|
||||
return err.Message
|
||||
}
|
||||
|
||||
func (err *APIError) UnmarshalJSON(data []byte) error {
|
||||
type APIError2 APIError
|
||||
|
||||
err2 := APIError2(*err)
|
||||
if jsonErr := json.Unmarshal(data, &err2); jsonErr != nil {
|
||||
return jsonErr
|
||||
}
|
||||
|
||||
switch err2.Code {
|
||||
case "invalid_request_body":
|
||||
var errData InvalidRequestBodyError
|
||||
|
||||
if err2.RawData != nil {
|
||||
if err := json.Unmarshal(err2.RawData, &errData); err != nil {
|
||||
return fmt.Errorf("invalid jsv errors: %w", err)
|
||||
}
|
||||
|
||||
err2.Data = &errData
|
||||
}
|
||||
}
|
||||
|
||||
*err = APIError(err2)
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsInvalidRequestBodyError(err error) (bool, check.ValidationErrors) {
|
||||
var apiError *APIError
|
||||
|
||||
if !errors.As(err, &apiError) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
requestBodyErr, ok := apiError.Data.(*InvalidRequestBodyError)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, requestBodyErr.ValidationErrors
|
||||
}
|
||||
|
||||
type ProjectPage struct {
|
||||
Elements eventline.Projects `json:"elements"`
|
||||
Previous *eventline.Cursor `json:"previous,omitempty"`
|
||||
Next *eventline.Cursor `json:"next,omitempty"`
|
||||
}
|
||||
|
||||
type Parameter struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Default interface{} `json:"default"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type Parameters []*Parameter
|
||||
|
||||
type JobPage struct {
|
||||
Elements eventline.Jobs `json:"elements"`
|
||||
Previous *eventline.Cursor `json:"previous,omitempty"`
|
||||
Next *eventline.Cursor `json:"next,omitempty"`
|
||||
}
|
332
external/evcli/client.go
vendored
Normal file
332
external/evcli/client.go
vendored
Normal file
|
@ -0,0 +1,332 @@
|
|||
package evcli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"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 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 := ioutil.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: 1}
|
||||
|
||||
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) 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)
|
||||
}
|
6
external/evcli/config.go
vendored
Normal file
6
external/evcli/config.go
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
package evcli
|
||||
|
||||
type APIConfig struct {
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
}
|
14
external/evcli/http.go
vendored
Normal file
14
external/evcli/http.go
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
package evcli
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewHTTPClient() *http.Client {
|
||||
c := &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
42
external/evcli/url.go
vendored
Normal file
42
external/evcli/url.go
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
package evcli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// The url package has an extremely confusing interface. One could believe
|
||||
// setting RawPath is enough for it to be used during encoding, but this is
|
||||
// not the case. Instead, *both* must be set, and (*url.URL).EscapedPath will
|
||||
// check that RawPath is a valid encoding of RawPath. If this is not the case,
|
||||
// RawPath will be ignored.
|
||||
//
|
||||
// In most cases, the problem is not apparent. But if the path contains
|
||||
// segments with slash or space characters, it is almost guaranteed to misuse
|
||||
// Path and RawPath and double-encode these characters.
|
||||
//
|
||||
// The problem was signaled years ago on
|
||||
// https://github.com/golang/go/issues/17340 but was of course ignored and
|
||||
// buried.
|
||||
//
|
||||
// As usual with standard library issues, the only thing we can do is add
|
||||
// utils to work around it.
|
||||
|
||||
func NewURL(pathSegments ...string) *url.URL {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte('/')
|
||||
for i, s := range pathSegments {
|
||||
if i > 0 {
|
||||
buf.WriteByte('/')
|
||||
}
|
||||
buf.WriteString(url.PathEscape(s))
|
||||
}
|
||||
rawPath := buf.String()
|
||||
|
||||
path, _ := url.PathUnescape(rawPath)
|
||||
|
||||
return &url.URL{
|
||||
Path: path,
|
||||
RawPath: rawPath,
|
||||
}
|
||||
}
|
23
external/evcli/url_test.go
vendored
Normal file
23
external/evcli/url_test.go
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
package evcli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewURL(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
assert.Equal("/",
|
||||
NewURL().String())
|
||||
|
||||
assert.Equal("/a",
|
||||
NewURL("a").String())
|
||||
|
||||
assert.Equal("/a/bcd/ef",
|
||||
NewURL("a", "bcd", "ef").String())
|
||||
|
||||
assert.Equal("/a/b%20c/e%2Ff",
|
||||
NewURL("a", "b c", "e/f").String())
|
||||
}
|
|
@ -5,7 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.adyxax.org/adyxax/terraform-eventline/internal/evcli"
|
||||
"git.adyxax.org/adyxax/terraform-eventline/external/evcli"
|
||||
"github.com/exograd/eventline/pkg/eventline"
|
||||
"github.com/exograd/go-daemon/ksuid"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.adyxax.org/adyxax/terraform-eventline/internal/evcli"
|
||||
"git.adyxax.org/adyxax/terraform-eventline/external/evcli"
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.adyxax.org/adyxax/terraform-eventline/internal/evcli"
|
||||
"git.adyxax.org/adyxax/terraform-eventline/external/evcli"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/provider"
|
||||
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
|
||||
|
@ -57,13 +57,12 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest,
|
|||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
config := evcli.Config{API: evcli.APIConfig{Endpoint: data.Endpoint.ValueString()}}
|
||||
config := evcli.APIConfig{Endpoint: data.Endpoint.ValueString(), Key: data.ApiKey.ValueString()}
|
||||
client, err := evcli.NewClient(&config)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("new api client", fmt.Sprintf("Unable to instanciate eventline api client, got error: %s", err))
|
||||
return
|
||||
}
|
||||
client.APIKey = data.ApiKey.ValueString()
|
||||
|
||||
resp.DataSourceData = client
|
||||
resp.ResourceData = client
|
||||
|
|
Loading…
Add table
Reference in a new issue