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
4 changes: 3 additions & 1 deletion cmd/seitask/main.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Command seitask is the monolithic Workflow-Task primitive binary: one
// binary, multiple urfave/cli subcommands (keygen, provision-snd, …) that
// binary, multiple urfave/cli subcommands (keygen, provision-snd,
// provision-node, …) that
// share the internal/taskruntime shared library. See
// https://github.com/sei-protocol/bdchatham-designs/blob/main/designs/test-harness/test-harness-lld.md.
package main
Expand Down Expand Up @@ -44,6 +45,7 @@ func main() {
Commands: []*cli.Command{
newKeygenCommand(),
newProvisionSNDCommand(),
newProvisionNodeCommand(),
newRunnerCommand(),
newUploadReportCommand(),
},
Expand Down
16 changes: 14 additions & 2 deletions cmd/seitask/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
seiv1alpha1 "github.com/sei-protocol/sei-k8s-controller/api/v1alpha1"
)

const apiGroup = "sei.io"

// TestTaskScheme_RoundTripsSeiNetwork would have caught the first manual fire's
// `no kind is registered for the type v1alpha1.SeiNetwork in scheme`
// regression at `go test`, not at first cluster fire. Asserts the
Expand All @@ -19,17 +21,27 @@ func TestTaskScheme_RoundTripsSeiNetwork(t *testing.T) {
if len(gvks) == 0 {
t.Fatalf("no GVKs returned for SeiNetwork")
}
if gvks[0].Group != "sei.io" || gvks[0].Version != "v1alpha1" {
if gvks[0].Group != apiGroup || gvks[0].Version != "v1alpha1" {
t.Fatalf("SeiNetwork GVK: %+v; want sei.io/v1alpha1", gvks[0])
}
}

func TestTaskScheme_RoundTripsSeiNode(t *testing.T) {
gvks, _, err := taskScheme.ObjectKinds(&seiv1alpha1.SeiNode{})
if err != nil {
t.Fatalf("SeiNode not registered in taskScheme: %v", err)
}
if len(gvks) == 0 || gvks[0].Group != apiGroup || gvks[0].Version != "v1alpha1" {
t.Fatalf("SeiNode GVK wrong: %+v; want sei.io/v1alpha1", gvks)
}
}

func TestTaskScheme_RoundTripsSeiNodeTask(t *testing.T) {
gvks, _, err := taskScheme.ObjectKinds(&seiv1alpha1.SeiNodeTask{})
if err != nil {
t.Fatalf("SeiNodeTask not registered in taskScheme: %v", err)
}
if len(gvks) == 0 || gvks[0].Group != "sei.io" {
if len(gvks) == 0 || gvks[0].Group != apiGroup {
t.Fatalf("SeiNodeTask GVK wrong: %+v", gvks)
}
}
121 changes: 121 additions & 0 deletions cmd/seitask/provision_node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package main

import (
"context"
"log"
"strings"
"time"

"github.com/urfave/cli/v3"

"github.com/sei-protocol/sei-k8s-controller/internal/seitask/provisionnode"
"github.com/sei-protocol/sei-k8s-controller/internal/taskruntime"
)

// Flag names shared across the template-rendering subcommands (provision-snd,
// provision-node, runner), declared once so goconst stays green.
const (
flagTemplate = "template"
flagVar = "var"
)

func newProvisionNodeCommand() *cli.Command {
return &cli.Command{
Name: "provision-node",
Usage: "Fan out N standalone SeiNode followers from a template, wait for " +
"Running + per-node TM/EVM readiness, and publish role-scoped endpoints",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "role",
Usage: "Role tag for workflow-vars keys (e.g. rpc); uppercased to RPC_*",
Sources: cli.EnvVars("ROLE"),
Required: true,
},
&cli.StringFlag{
Name: "name",
Usage: "Base name; the N followers are <name>-0..<name>-(N-1) (defaults to <chainId>-<role>)",
Sources: cli.EnvVars("NODE_NAME"),
},
&cli.StringFlag{
Name: flagTemplate,
Usage: "Path to the Go text/template producing one kind: SeiNode YAML",
Sources: cli.EnvVars("NODE_TEMPLATE"),
Required: true,
},
&cli.StringSliceFlag{
Name: flagVar,
Usage: "KEY=VALUE substitution as .KEY (repeatable); .ORDINAL and .NODE_NAME are runtime-injected",
},
&cli.IntFlag{
Name: "replicas",
Usage: "N: number of follower SeiNode CRs to fan out",
Sources: cli.EnvVars("NODE_REPLICAS"),
Value: 1,
},
&cli.StringFlag{
Name: "network",
Usage: "Genesis SeiNetwork to follow; drives peer auto-wiring + the sei.io/seinetwork object label",
Sources: cli.EnvVars("NETWORK"),
},
&cli.StringFlag{
Name: "network-namespace",
Usage: "Namespace of the genesis SeiNetwork for the synthesized peer selector (defaults to the workflow namespace)",
},
&cli.DurationFlag{
Name: "running-timeout",
Usage: "Max wait for all N SeiNodes to reach status.phase=Running",
Value: 15 * time.Minute,
},
&cli.DurationFlag{
Name: "first-block-timeout",
Usage: "Per-node post-Running readiness budget (TM /status height>0 and EVM eth_blockNumber 200)",
Value: 5 * time.Minute,
},
&cli.DurationFlag{
Name: "poll-interval",
Usage: "Status + RPC poll cadence",
Value: 5 * time.Second,
},
},
Action: runProvisionNode,
}
}

func runProvisionNode(ctx context.Context, cmd *cli.Command) error {
c, err := kubeClientFromEnv()
if err != nil {
return err
}
wf, err := taskruntime.LoadWorkflowIdentity(ctx, c)
if err != nil {
return err
}

vars, err := parseKVPairs(cmd.StringSlice(flagVar))
if err != nil {
return err
}

p := provisionnode.Params{
Role: cmd.String("role"),
Name: cmd.String("name"),
TemplatePath: cmd.String(flagTemplate),
Vars: vars,
Replicas: cmd.Int("replicas"),
Network: cmd.String("network"),
NetworkNamespace: cmd.String("network-namespace"),
RunningTimeout: cmd.Duration("running-timeout"),
FirstBlockTimeout: cmd.Duration("first-block-timeout"),
PollInterval: cmd.Duration("poll-interval"),
Workflow: wf,
}
res, err := provisionnode.Run(ctx, c, p)
if err != nil {
taskruntime.WriteExitReason(ctx, c, wf, err)
return err
}
taskruntime.WriteExitReason(ctx, c, wf, nil)
log.Printf("provision-node: %d SeiNode(s) Running [%s], chainID=%s, EVM_RPC_LIST=%s",
len(res.Names), strings.Join(res.Names, ","), res.ChainID, res.EVMRPCList)
return nil
}
8 changes: 4 additions & 4 deletions cmd/seitask/provision_snd.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ func newProvisionSNDCommand() *cli.Command {
Sources: cli.EnvVars("SND_NAME"),
},
&cli.StringFlag{
Name: "template",
Name: flagTemplate,
Usage: "Path to the Go text/template producing a SeiNetwork YAML",
Sources: cli.EnvVars("SND_TEMPLATE"),
Required: true,
},
&cli.StringSliceFlag{
Name: "var",
Name: flagVar,
Usage: "KEY=VALUE substitution exposed to the template as .KEY (repeatable)",
},
&cli.DurationFlag{
Expand Down Expand Up @@ -65,15 +65,15 @@ func runProvisionSND(ctx context.Context, cmd *cli.Command) error {
return err
}

vars, err := parseKVPairs(cmd.StringSlice("var"))
vars, err := parseKVPairs(cmd.StringSlice(flagVar))
if err != nil {
return err
}

p := provisionsnd.Params{
Role: cmd.String("role"),
Name: cmd.String("name"),
TemplatePath: cmd.String("template"),
TemplatePath: cmd.String(flagTemplate),
Vars: vars,
ReadyTimeout: cmd.Duration("ready-timeout"),
FirstBlockTimeout: cmd.Duration("first-block-timeout"),
Expand Down
8 changes: 4 additions & 4 deletions cmd/seitask/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ func newRunnerCommand() *cli.Command {
Usage: "Apply a SeiNodeTask CR from a template and poll until terminal",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "template",
Name: flagTemplate,
Usage: "Path to the Go text/template producing a SeiNodeTask manifest (required)",
Required: true,
},
&cli.StringSliceFlag{
Name: "var",
Name: flagVar,
Usage: "KEY=VALUE substitution exposed to the template as .KEY (repeatable)",
},
&cli.StringSliceFlag{
Expand Down Expand Up @@ -80,7 +80,7 @@ func newRunnerCommand() *cli.Command {
}

func runRunner(ctx context.Context, cmd *cli.Command) error {
varMap, err := parseKVSlice(cmd.StringSlice("var"))
varMap, err := parseKVSlice(cmd.StringSlice(flagVar))
if err != nil {
return err
}
Expand Down Expand Up @@ -117,7 +117,7 @@ func runRunner(ctx context.Context, cmd *cli.Command) error {

r := &runner.Run{
Opts: runner.Options{
TemplatePath: cmd.String("template"),
TemplatePath: cmd.String(flagTemplate),
Vars: varMap,
OutputJSONPaths: cmd.StringSlice("output-jsonpath"),
OutputEnvFile: cmd.String("output-env-file"),
Expand Down
Loading
Loading