diff --git a/framework/components/chiprouter/.changeset/v1.0.2.md b/framework/components/chiprouter/.changeset/v1.0.2.md new file mode 100644 index 000000000..09ac07126 --- /dev/null +++ b/framework/components/chiprouter/.changeset/v1.0.2.md @@ -0,0 +1 @@ +- Add ChIP Router client \ No newline at end of file diff --git a/framework/components/chiprouter/client/client.go b/framework/components/chiprouter/client/client.go new file mode 100644 index 000000000..25fa90031 --- /dev/null +++ b/framework/components/chiprouter/client/client.go @@ -0,0 +1,134 @@ +package client + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net" + "net/http" + "strings" + "time" + + pkgerrors "github.com/pkg/errors" +) + +const ( + adminRequestTimeout = 5 * time.Second +) + +type RegisterSubscriberRequest struct { + Name string `json:"name"` + Endpoint string `json:"endpoint"` +} + +type RegisterSubscriberResponse struct { + ID string `json:"id"` +} + +type Client struct { + httpClient *http.Client + adminURL string + grpcURL string +} + +func New(ctx context.Context, adminURL, grpcURL string) (*Client, error) { + c := &Client{ + httpClient: &http.Client{Timeout: adminRequestTimeout}, + adminURL: adminURL, + grpcURL: grpcURL, + } + + if !c.isHTTPReady(ctx) { + return nil, fmt.Errorf("chip router admin endpoint is not reachable: %s", c.adminURL) + } + if !c.isTCPReady() { + return nil, fmt.Errorf("chip router grpc endpoint is not reachable: %s", c.grpcURL) + } + return c, nil +} + +func (c *Client) RegisterSubscriber(ctx context.Context, name, endpoint string) (string, error) { + body, err := json.Marshal(RegisterSubscriberRequest{Name: name, Endpoint: endpoint}) + if err != nil { + return "", pkgerrors.Wrap(err, "marshal chip router register request") + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, strings.TrimRight(c.adminURL, "/")+"/subscribers", bytes.NewReader(body)) + if err != nil { + return "", pkgerrors.Wrap(err, "create chip router register request") + } + req.Header.Set("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return "", pkgerrors.Wrap(err, "perform chip router register request") + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("chip router register request failed with status %s", resp.Status) + } + + var out RegisterSubscriberResponse + if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { + return "", pkgerrors.Wrap(err, "decode chip router register response") + } + if out.ID == "" { + return "", pkgerrors.New("chip router register response missing subscriber id") + } + + return out.ID, nil +} + +func (c *Client) UnregisterSubscriber(ctx context.Context, id string) error { + if strings.TrimSpace(id) == "" { + return nil + } + + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, strings.TrimRight(c.adminURL, "/")+"/subscribers/"+id, nil) + if err != nil { + return pkgerrors.Wrap(err, "create chip router unregister request") + } + + resp, err := c.httpClient.Do(req) + if err != nil { + return pkgerrors.Wrap(err, "perform chip router unregister request") + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("chip router unregister request failed with status %s", resp.Status) + } + return nil +} + +func (c *Client) isHTTPReady(ctx context.Context) bool { + if strings.TrimSpace(c.adminURL) == "" { + return false + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.TrimRight(c.adminURL, "/")+"/health", nil) + if err != nil { + return false + } + resp, err := c.httpClient.Do(req) + if err != nil { + return false + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return false + } + + return true +} + +func (c *Client) isTCPReady() bool { + dialer := &net.Dialer{Timeout: adminRequestTimeout} + conn, err := dialer.Dial("tcp", c.grpcURL) + if err != nil { + return false + } + _ = conn.Close() + return true +} diff --git a/framework/components/chiprouter/go.mod b/framework/components/chiprouter/go.mod index dca3eca19..d53d280e0 100644 --- a/framework/components/chiprouter/go.mod +++ b/framework/components/chiprouter/go.mod @@ -7,6 +7,7 @@ require ( github.com/docker/docker v28.5.2+incompatible github.com/docker/go-connections v0.6.0 github.com/google/uuid v1.6.0 + github.com/pkg/errors v0.9.1 github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20251211140724-319861e514c4 github.com/smartcontractkit/chainlink-testing-framework/framework v0.15.8 github.com/testcontainers/testcontainers-go v0.41.0 @@ -58,7 +59,6 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/rs/zerolog v1.33.0 // indirect