Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions cmd/workflow/simulate/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/capabilities/fakes"
)

// httpTriggerServerPort is the port on which the local HTTP server listens
// when no --http-payload flag is supplied and the user chooses to POST the payload.
const httpTriggerServerPort = 2000

// ManualTriggers holds chain-agnostic trigger services used in simulation.
type ManualTriggers struct {
ManualCronTrigger *fakes.ManualCronTriggerService
Expand All @@ -30,7 +26,7 @@ type ManualTriggers struct {

// NewManualTriggerCapabilities creates and registers cron and HTTP trigger capabilities.
// These are chain-agnostic and shared across all chain types.
func NewManualTriggerCapabilities(ctx context.Context, lggr logger.Logger, registry *capabilities.Registry) (*ManualTriggers, error) {
func NewManualTriggerCapabilities(ctx context.Context, lggr logger.Logger, registry *capabilities.Registry, httpTriggerPort int) (*ManualTriggers, error) {
manualCronTrigger, err := fakes.NewManualCronTriggerService(lggr)
if err != nil {
return nil, err
Expand All @@ -40,7 +36,7 @@ func NewManualTriggerCapabilities(ctx context.Context, lggr logger.Logger, regis
return nil, err
}

manualHTTPTrigger := NewManualHTTPTriggerService(lggr)
manualHTTPTrigger := NewManualHTTPTriggerService(lggr, httpTriggerPort)
manualHTTPTriggerServer := httptrigger.NewHTTPServer(manualHTTPTrigger)
if err := registry.Add(ctx, manualHTTPTriggerServer); err != nil {
return nil, err
Expand Down
6 changes: 4 additions & 2 deletions cmd/workflow/simulate/manual_http_trigger.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@ type ManualHTTPTriggerService struct {
workflowIDs map[string]string
inputs map[string]*httptypedapi.Config
eventSeq uint64
port int
}

func NewManualHTTPTriggerService(parentLggr logger.Logger) *ManualHTTPTriggerService {
func NewManualHTTPTriggerService(parentLggr logger.Logger, port int) *ManualHTTPTriggerService {
return &ManualHTTPTriggerService{
CapabilityInfo: manualHTTPTriggerInfo,
lggr: logger.Named(parentLggr, "HTTPTriggerService"),
callbackCh: make(map[string]chan capabilities.TriggerAndId[*httptypedapi.Payload]),
workflowIDs: make(map[string]string),
inputs: make(map[string]*httptypedapi.Config),
port: port,
}
}

Expand Down Expand Up @@ -121,7 +123,7 @@ func (f *ManualHTTPTriggerService) ManualTrigger(ctx context.Context, triggerID
}

func (f *ManualHTTPTriggerService) listenForTriggerPayload(ctx context.Context) (*httptypedapi.Payload, error) {
payloadCh, closeServer, err := startHTTPListenPayloadServer(ctx, httpTriggerServerPort)
payloadCh, closeServer, err := startHTTPListenPayloadServer(ctx, f.port)
if err != nil {
return nil, err
}
Expand Down
23 changes: 16 additions & 7 deletions cmd/workflow/simulate/simulate.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
)

const WorkflowExecutionTimeout = 5 * time.Minute
const defaultHTTPTriggerServerPort = 2000

type Inputs struct {
WasmPath string `validate:"omitempty,file,ascii,max=97" cli:"--wasm"`
Expand All @@ -65,6 +66,7 @@ type Inputs struct {
HasTriggerIndex bool
TriggerIndex int `validate:"-"`
HTTPPayload string `validate:"-"` // JSON string or /path/to/file.json
HTTPTriggerPort int `validate:"min=1,max=65535"`
ChainTypeInputs map[string]string `validate:"-"` // CLI-supplied chain-type-specific trigger inputs
// Listen keeps the HTTP trigger server running after each execution so it can
// process additional requests until the user interrupts (ctrl-C).
Expand Down Expand Up @@ -111,7 +113,8 @@ func New(runtimeContext *runtime.Context) *cobra.Command {
// Non-interactive trigger selection flags
simulateCmd.Flags().Int("trigger-index", -1, "Index of the trigger to run (0-based)")
simulateCmd.Flags().String("http-payload", "", "HTTP trigger payload as JSON string or path to JSON file")
simulateCmd.Flags().Bool("listen", false, "Listen for HTTP requests or supported log triggers and run the simulator for each match")
simulateCmd.Flags().Int("http-trigger-port", defaultHTTPTriggerServerPort, "Port used by the local HTTP trigger server")
simulateCmd.Flags().Bool("listen", false, "Listen for HTTP requests or supported log triggers and run the simulator for each match (not supported by cron)")

// Register chain-type-specific CLI flags (e.g., --evm-tx-hash).
chain.RegisterAllCLIFlags(simulateCmd)
Expand Down Expand Up @@ -190,6 +193,11 @@ func (h *handler) ResolveInputs(v *viper.Viper, creSettings *settings.Settings)
}
}

httpTriggerPort := v.GetInt("http-trigger-port")
if !v.IsSet("http-trigger-port") {
httpTriggerPort = defaultHTTPTriggerServerPort
}

return Inputs{
WasmPath: v.GetString("wasm"),
WorkflowPath: creSettings.Workflow.WorkflowArtifactSettings.WorkflowPath,
Expand All @@ -205,6 +213,7 @@ func (h *handler) ResolveInputs(v *viper.Viper, creSettings *settings.Settings)
HasTriggerIndex: v.IsSet("trigger-index"),
TriggerIndex: v.GetInt("trigger-index"),
HTTPPayload: v.GetString("http-payload"),
HTTPTriggerPort: httpTriggerPort,
ChainTypeInputs: chain.CollectAllCLIInputs(v),
Listen: v.GetBool("listen"),
LimitsPath: v.GetString("limits"),
Expand Down Expand Up @@ -477,7 +486,7 @@ func run(
// Register chain-agnostic cron and HTTP triggers
triggerLggr := lggr.Named("TriggerCapabilities")
var err error
manualTriggerCaps, err = NewManualTriggerCapabilities(ctx, triggerLggr, registry)
manualTriggerCaps, err = NewManualTriggerCapabilities(ctx, triggerLggr, registry, inputs.HTTPTriggerPort)
if err != nil {
ui.Error(fmt.Sprintf("Failed to create trigger capabilities: %v", err))
os.Exit(1)
Expand Down Expand Up @@ -714,7 +723,7 @@ func runHTTPListen(ctx context.Context, inputs Inputs, triggerInfo *TriggerInfoA
os.Exit(1)
}

payloadCh, closeServer, err := startHTTPListenPayloadServer(ctx, httpTriggerServerPort)
payloadCh, closeServer, err := startHTTPListenPayloadServer(ctx, inputs.HTTPTriggerPort)
if err != nil {
ui.Error(fmt.Sprintf("Failed to start HTTP trigger server: %v", err))
os.Exit(1)
Expand Down Expand Up @@ -769,7 +778,7 @@ func runHTTPListen(ctx context.Context, inputs Inputs, triggerInfo *TriggerInfoA
ui.Line()
ui.Step(fmt.Sprintf("Listen: ready for next request (run #%d)", iteration+1))
}
ui.Step(fmt.Sprintf("Waiting for HTTP request to start execution (listening on http://localhost:%d/trigger)...", httpTriggerServerPort))
ui.Step(fmt.Sprintf("Waiting for HTTP request to start execution (listening on http://localhost:%d/trigger)...", inputs.HTTPTriggerPort))

var payload *httptypedapi.Payload
select {
Expand Down Expand Up @@ -946,7 +955,7 @@ func makeBeforeStartInteractive(holder *TriggerInfoAndBeforeStart, inputs Inputs
ui.Line()
ui.Step("No input detected for http-trigger. Supply the payload using one of:")
ui.Dim("1. POST JSON to the local trigger server, example:")
ui.Dim(fmt.Sprintf(` curl -X POST http://localhost:%d/trigger \`, httpTriggerServerPort))
ui.Dim(fmt.Sprintf(` curl -X POST http://localhost:%d/trigger \`, inputs.HTTPTriggerPort))
ui.Dim(" -H 'Content-Type: application/json' \\")
ui.Dim(" -d '{\"input\":{\"key\":\"value\"}}'")
ui.Dim("2. Re-run with --http-payload flag:")
Expand All @@ -960,7 +969,7 @@ func makeBeforeStartInteractive(holder *TriggerInfoAndBeforeStart, inputs Inputs
p := payload
payload = nil
if p == nil {
ui.Step(fmt.Sprintf("Waiting for HTTP request to start execution (listening on http://localhost:%d/trigger)...", httpTriggerServerPort))
ui.Step(fmt.Sprintf("Waiting for HTTP request to start execution (listening on http://localhost:%d/trigger)...", inputs.HTTPTriggerPort))
}
return manualTriggerCaps.ManualHTTPTrigger.ManualTrigger(ctx, triggerRegistrationID, p)
}
Expand Down Expand Up @@ -1080,7 +1089,7 @@ func makeBeforeStartNonInteractive(holder *TriggerInfoAndBeforeStart, inputs Inp
p := payload
payload = nil
if p == nil {
ui.Step(fmt.Sprintf("Waiting for HTTP request to start execution (listening on http://localhost:%d/trigger)...", httpTriggerServerPort))
ui.Step(fmt.Sprintf("Waiting for HTTP request to start execution (listening on http://localhost:%d/trigger)...", inputs.HTTPTriggerPort))
}
return manualTriggerCaps.ManualHTTPTrigger.ManualTrigger(ctx, triggerRegistrationID, p)
}
Expand Down
52 changes: 46 additions & 6 deletions cmd/workflow/simulate/simulate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,45 @@ func TestSimulateResolveInputs_WasmFlag(t *testing.T) {
})
}

func TestSimulateResolveInputs_HTTPTriggerPort(t *testing.T) {
t.Parallel()

t.Run("default port when flag unset", func(t *testing.T) {
t.Parallel()
v := createSimulateTestViper(t)
creSettings := createSimulateTestSettings("test-workflow", "main.go", "")

runtimeCtx := &runtime.Context{
Logger: testutil.NewTestLogger(),
Viper: v,
Settings: creSettings,
}
h := newHandler(runtimeCtx)

inputs, err := h.ResolveInputs(v, creSettings)
require.NoError(t, err)
assert.Equal(t, defaultHTTPTriggerServerPort, inputs.HTTPTriggerPort)
})

t.Run("flag overrides default", func(t *testing.T) {
t.Parallel()
v := createSimulateTestViper(t)
v.Set("http-trigger-port", 3210)
creSettings := createSimulateTestSettings("test-workflow", "main.go", "")

runtimeCtx := &runtime.Context{
Logger: testutil.NewTestLogger(),
Viper: v,
Settings: creSettings,
}
h := newHandler(runtimeCtx)

inputs, err := h.ResolveInputs(v, creSettings)
require.NoError(t, err)
assert.Equal(t, 3210, inputs.HTTPTriggerPort)
})
}

func TestSimulateValidateInputs_URLBypass(t *testing.T) {
t.Parallel()

Expand All @@ -268,10 +307,11 @@ func TestSimulateValidateInputs_URLBypass(t *testing.T) {
h := newHandler(runtimeCtx)

inputs := Inputs{
WorkflowPath: tmpFile,
ConfigPath: "https://example.com/config.yaml",
WasmPath: "https://example.com/binary.wasm",
WorkflowName: "test-workflow",
WorkflowPath: tmpFile,
ConfigPath: "https://example.com/config.yaml",
WasmPath: "https://example.com/binary.wasm",
HTTPTriggerPort: defaultHTTPTriggerServerPort,
WorkflowName: "test-workflow",
}

err := h.ValidateInputs(inputs)
Expand Down Expand Up @@ -487,7 +527,7 @@ func TestNonInteractiveCronTriggerDoesNotBlockOnSchedule(t *testing.T) {
require.Nil(t, capErr)

holder := &TriggerInfoAndBeforeStart{}
inputs := Inputs{TriggerIndex: triggerIndex}
inputs := Inputs{TriggerIndex: triggerIndex, HTTPTriggerPort: defaultHTTPTriggerServerPort}
manualTriggers := &ManualTriggers{ManualCronTrigger: cronSvc}

beforeStart := makeBeforeStartNonInteractive(holder, inputs, func() *ManualTriggers {
Expand Down Expand Up @@ -545,7 +585,7 @@ func TestHTTPListenPayloadServerAcceptsMultipleRequests(t *testing.T) {
func TestManualHTTPTriggerEventsHaveUniqueIDs(t *testing.T) {
t.Parallel()

svc := NewManualHTTPTriggerService(logger.Test(t))
svc := NewManualHTTPTriggerService(logger.Test(t), defaultHTTPTriggerServerPort)
first := svc.createManualTriggerEvent(nil)
second := svc.createManualTriggerEvent(nil)

Expand Down
3 changes: 2 additions & 1 deletion docs/cre_workflow_simulate.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ cre workflow simulate ./my-workflow
--evm-tx-hash string EVM trigger transaction hash (0x...)
-h, --help help for simulate
--http-payload string HTTP trigger payload as JSON string or path to JSON file
--http-trigger-port int Port used by the local HTTP trigger server (default 2000)
--limits string Production limits to enforce during simulation: 'default' for prod defaults, path to a limits JSON file (e.g. from 'cre workflow limits export'), or 'none' to disable (default "default")
--listen Listen for HTTP requests or supported log triggers and run the simulator for each match
--listen Listen for HTTP requests or supported log triggers and run the simulator for each match (not supported by cron)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

--no-config Simulate without a config file
--skip-type-checks Skip TypeScript project typecheck during compilation (passes --skip-type-checks to cre-compile)
--trigger-index int Index of the trigger to run (0-based) (default -1)
Expand Down
Loading