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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ require (
github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249
github.com/oklog/run v1.1.0
github.com/panta/machineid v1.0.2
github.com/signadot/go-sdk v0.3.8-0.20260505200838-3bae35b0d0d8
github.com/signadot/go-sdk v0.3.8-0.20260507124501-af142d7ec85c
github.com/signadot/libconnect v0.1.1-0.20260424105947-336dce43da75
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.11.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -452,8 +452,8 @@ github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfv
github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/signadot/go-sdk v0.3.8-0.20260505200838-3bae35b0d0d8 h1:KFayX3D9o1XaDA8JNIngOVEDc0t35Y5pERsAn3Tti8A=
github.com/signadot/go-sdk v0.3.8-0.20260505200838-3bae35b0d0d8/go.mod h1:8CfvBQ/AQ3LPruaQZoflmpBjoZTwXfhBt2OtS1eet+Q=
github.com/signadot/go-sdk v0.3.8-0.20260507124501-af142d7ec85c h1:1FYXrhR0NXcxC9uwwrZVU4A58mImiJ1yUACVUfoNJyY=
github.com/signadot/go-sdk v0.3.8-0.20260507124501-af142d7ec85c/go.mod h1:8CfvBQ/AQ3LPruaQZoflmpBjoZTwXfhBt2OtS1eet+Q=
github.com/signadot/libconnect v0.1.1-0.20260424105947-336dce43da75 h1:LZJrEqJeSb0CGsENOAQsvDeEO2YyMY1/ir2Nz4apvbI=
github.com/signadot/libconnect v0.1.1-0.20260424105947-336dce43da75/go.mod h1:cAsgAummH9Q9DrLQ7+S3mqrBv/+ZYKVSEXjR/WfoUJM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
Expand Down
221 changes: 1 addition & 220 deletions internal/command/plan/printers.go
Original file line number Diff line number Diff line change
@@ -1,231 +1,12 @@
package plan

import (
"fmt"
"io"
"sort"
"text/tabwriter"

"github.com/signadot/cli/internal/command/planshared"
"github.com/signadot/cli/internal/print"
"github.com/signadot/cli/internal/utils"
"github.com/signadot/go-sdk/models"
)

func printPlanDetails(out io.Writer, p *models.RunnablePlan) error {
tw := tabwriter.NewWriter(out, 0, 0, 3, ' ', 0)

fmt.Fprintf(tw, "ID:\t%s\n", p.ID)
if p.Spec != nil {
if p.Spec.Prompt != "" {
fmt.Fprintf(tw, "Prompt:\t%s\n", print.FirstLine(p.Spec.Prompt))
}
if p.Spec.Runner != "" {
fmt.Fprintf(tw, "Runner:\t%s\n", p.Spec.Runner)
}
if c := p.Spec.Cluster; c != nil {
switch {
case c.FromCluster != "":
fmt.Fprintf(tw, "Cluster:\tfrom param %q\n", c.FromCluster)
case c.FromSandbox != "":
fmt.Fprintf(tw, "Cluster:\tfrom sandbox param %q\n", c.FromSandbox)
case c.FromRouteGroup != "":
fmt.Fprintf(tw, "Cluster:\tfrom route group param %q\n", c.FromRouteGroup)
case c.Pattern != "":
fmt.Fprintf(tw, "Cluster:\tpattern %q\n", c.Pattern)
}
}
}
if p.Status != nil {
fmt.Fprintf(tw, "Created:\t%s\n", utils.FormatTimestamp(p.Status.CreatedAt))
if p.Status.CompiledFrom != "" {
fmt.Fprintf(tw, "Compiled From:\t%s\n", p.Status.CompiledFrom)
}
if p.Status.Executions > 0 {
fmt.Fprintf(tw, "Executions:\t%d\n", p.Status.Executions)
}
}
if err := tw.Flush(); err != nil {
return err
}

if p.Spec == nil {
return nil
}

if len(p.Spec.Params) > 0 {
fmt.Fprintln(out)
fmt.Fprintln(out, "Inputs:")
printPlanParams(out, p.Spec.Params)
}

if len(p.Spec.Steps) > 0 {
fmt.Fprintln(out)
fmt.Fprintln(out, "Steps:")
printPlanSteps(out, p.Spec.Steps)
}

if len(p.Spec.Output) > 0 {
fmt.Fprintln(out)
fmt.Fprintln(out, "Outputs:")
printPlanOutputs(out, p.Spec.Output)
}

return nil
}

// printPlanParams renders each declared param. Defaults render with
// the arrow form used elsewhere; required params with no default get
// a "(required)" tag, optional ones with no default a "(optional)".
func printPlanParams(out io.Writer, params []*models.PlanField) {
maxName := 0
for _, p := range params {
if p != nil && len(p.Name) > maxName {
maxName = len(p.Name)
}
}
for _, p := range params {
if p == nil {
continue
}
var detail, via string
switch {
case p.Default != nil:
detail = planshared.FormatValue(p.Default)
via = "default"
case p.Required:
via = "required"
default:
via = "optional"
}
planshared.PrintInputLine(out, " ", maxName, p.Name, detail, via)
}
}

func printPlanOutputs(out io.Writer, outputs map[string]string) {
names := make([]string, 0, len(outputs))
for k := range outputs {
names = append(names, k)
}
sort.Strings(names)
maxName := 0
for _, n := range names {
if len(n) > maxName {
maxName = len(n)
}
}
// Plan outputs are always refs (mappings to step outputs), so we
// drop the trailing (ref) tag — there's no other variant to
// disambiguate against.
for _, n := range names {
fmt.Fprintf(out, " %-*s ← %s\n", maxName, n, outputs[n])
}
}

func printPlanSteps(out io.Writer, steps []*models.PlanStep) {
for i, s := range steps {
if s == nil {
continue
}
if i > 0 {
fmt.Fprintln(out)
}
fmt.Fprintf(out, " %s\n", s.ID)
if s.Action != nil {
line := " action: " + planshared.ActionLabel(s.Action)
if s.Action.Revision > 0 {
line += fmt.Sprintf(" (revision %d)", s.Action.Revision)
}
fmt.Fprintln(out, line)
if img := planshared.FormatImage(s.Action.Image); img != "" {
fmt.Fprintf(out, " image: %s\n", img)
}
if s.Action.Timeout != "" {
fmt.Fprintf(out, " timeout: %s\n", s.Action.Timeout)
} else if s.Action.TimeoutInputName != "" {
fmt.Fprintf(out, " timeout: (from input %q)\n", s.Action.TimeoutInputName)
}
}
if s.Condition != "" {
fmt.Fprintf(out, " when: %s\n", s.Condition)
}
printStepInputs(out, s)
printStepOutputs(out, s)
}
}

// printStepInputs lists the inputs the plan author wired for this
// step. Values from step.Args.Values render as "set in plan"; refs
// from step.Args.Refs render as "ref". Inputs that were neither set
// nor wired (will fall back to defaults or be unbound at run time)
// aren't shown here; that resolution is per-execution and lives in
// plan x get.
func printStepInputs(out io.Writer, s *models.PlanStep) {
values := planshared.ParamsAsMap(planshared.StepArgsValues(s))
var refs map[string]string
if s.Args != nil {
refs = s.Args.Refs
}
if len(values) == 0 && len(refs) == 0 {
return
}

type wired struct {
name, detail, via string
}
var inputs []wired
for name, v := range values {
inputs = append(inputs, wired{name, planshared.FormatValue(v), "set in plan"})
}
for name, r := range refs {
inputs = append(inputs, wired{name, r, "ref"})
}
sort.Slice(inputs, func(i, j int) bool { return inputs[i].name < inputs[j].name })

fmt.Fprintln(out, " inputs:")
maxName := 0
for _, in := range inputs {
if len(in.name) > maxName {
maxName = len(in.name)
}
}
for _, in := range inputs {
planshared.PrintInputLine(out, " ", maxName, in.name, in.detail, in.via)
}
}

// printStepOutputs lists the step's declared extra_outputs (the step-
// level extension over the action's declared outputs) with their
// schema summary. The action's own outputs are inherent to the action
// and visible via signadot plan action get.
func printStepOutputs(out io.Writer, s *models.PlanStep) {
if len(s.ExtraOutputs) == 0 {
return
}
fmt.Fprintln(out, " outputs:")
maxName := 0
for _, o := range s.ExtraOutputs {
if o != nil && len(o.Name) > maxName {
maxName = len(o.Name)
}
}
for _, o := range s.ExtraOutputs {
if o == nil {
continue
}
schema := formatFieldSchema(o)
fmt.Fprintf(out, " %-*s %s\n", maxName, o.Name, schema)
}
}

func formatFieldSchema(f *models.PlanField) string {
if f.SchemaRef != "" {
return fmt.Sprintf("schema: %s", f.SchemaRef)
}
if m, ok := f.Schema.(map[string]any); ok {
if t, ok := m["type"].(string); ok && t != "" {
return fmt.Sprintf("schema: %s", t)
}
}
return ""
return planshared.PrintPlanDetails(out, p)
}
42 changes: 35 additions & 7 deletions internal/command/planaction/printers.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ func printActionDetails(out io.Writer, a *models.PlanAction) error {
}

if a.Status != nil {
if err := printFields(out, "Inputs", a.Status.BodyParams); err != nil {
if err := printInputFields(out, a.Status.BodyParams); err != nil {
return err
}
if err := printFields(out, "Outputs", a.Status.BodyOutputs); err != nil {
if err := printOutputFields(out, a.Status.BodyOutputs); err != nil {
return err
}
}
Expand Down Expand Up @@ -141,23 +141,26 @@ func enabled(a *models.PlanAction) string {
return fmt.Sprintf("%t", a.Status.Enabled)
}

type fieldRow struct {
type inputFieldRow struct {
Name string `sdtab:"NAME"`
Required string `sdtab:"REQUIRED"`
Default string `sdtab:"DEFAULT"`
Schema string `sdtab:"SCHEMA"`
}

func printFields(out io.Writer, label string, fields []*models.PlanField) error {
// printInputFields renders an action's declared input parameters.
// Inputs carry name, required-ness, default, and schema — all four
// columns are meaningful here.
func printInputFields(out io.Writer, fields []*models.PlanField) error {
if len(fields) == 0 {
return nil
}
fmt.Fprintln(out)
fmt.Fprintf(out, "%s:\n", label)
t := sdtab.New[fieldRow](out)
fmt.Fprintln(out, "Inputs:")
t := sdtab.New[inputFieldRow](out)
t.AddHeader()
for _, f := range fields {
t.AddRow(fieldRow{
t.AddRow(inputFieldRow{
Name: f.Name,
Required: fmt.Sprintf("%t", f.Required),
Default: formatAny(f.Default),
Expand All @@ -167,6 +170,31 @@ func printFields(out io.Writer, label string, fields []*models.PlanField) error
return t.Flush()
}

type outputFieldRow struct {
Name string `sdtab:"NAME"`
Schema string `sdtab:"SCHEMA"`
}

// printOutputFields renders an action's declared output fields.
// Outputs don't have required-ness or defaults (those are inputs
// concepts) — only name and schema apply.
func printOutputFields(out io.Writer, fields []*models.PlanField) error {
if len(fields) == 0 {
return nil
}
fmt.Fprintln(out)
fmt.Fprintln(out, "Outputs:")
t := sdtab.New[outputFieldRow](out)
t.AddHeader()
for _, f := range fields {
t.AddRow(outputFieldRow{
Name: f.Name,
Schema: formatSchema(f),
})
}
return t.Flush()
}

// formatAny renders a PlanField default for the table column. Scalars
// pass through fmt.Sprintf so a string default reads as its bare text;
// objects and arrays go through json.Marshal so they render as
Expand Down
9 changes: 1 addition & 8 deletions internal/command/planrunnergroup/printers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ import (
"fmt"
"io"
"text/tabwriter"
"time"

"github.com/signadot/cli/internal/sdtab"
"github.com/signadot/cli/internal/utils"
"github.com/signadot/go-sdk/models"
"github.com/xeonx/timeago"
)

type planRunnerGroupRow struct {
Expand All @@ -23,15 +21,10 @@ func printPlanRunnerGroupTable(out io.Writer, prgs []*models.PlanRunnerGroup) er
t := sdtab.New[planRunnerGroupRow](out)
t.AddHeader()
for _, prg := range prgs {
createdAt, err := time.Parse(time.RFC3339, prg.CreatedAt)
if err != nil {
return err
}

t.AddRow(planRunnerGroupRow{
Name: prg.Name,
Cluster: prg.Spec.Cluster,
Created: timeago.NoMax(timeago.English).Format(createdAt),
Created: utils.TimeAgo(prg.CreatedAt),
Status: readiness(prg),
})
}
Expand Down
Loading