mirror of
https://github.com/hanzo-fi/stack.git
synced 2026-10-30 20:34:43 +00:00
323 lines
12 KiB
Go
323 lines
12 KiB
Go
package instances
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
fctl "github.com/formancehq/fctl/pkg"
|
|
formance "github.com/formancehq/formance-sdk-go"
|
|
"github.com/formancehq/formance-sdk-go/pkg/models/operations"
|
|
"github.com/formancehq/formance-sdk-go/pkg/models/shared"
|
|
"github.com/pkg/errors"
|
|
"github.com/pterm/pterm"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
type InstancesDescribeStore struct {
|
|
WorkflowInstancesHistory []shared.WorkflowInstanceHistory `json:"workflowInstanceHistory"`
|
|
}
|
|
type InstancesDescribeController struct {
|
|
store *InstancesDescribeStore
|
|
client *formance.Formance
|
|
}
|
|
|
|
var _ fctl.Controller[*InstancesDescribeStore] = (*InstancesDescribeController)(nil)
|
|
|
|
func NewDefaultInstancesDescribeStore() *InstancesDescribeStore {
|
|
return &InstancesDescribeStore{}
|
|
}
|
|
|
|
func NewInstancesDescribeController() *InstancesDescribeController {
|
|
return &InstancesDescribeController{
|
|
store: NewDefaultInstancesDescribeStore(),
|
|
}
|
|
}
|
|
|
|
func NewDescribeCommand() *cobra.Command {
|
|
c := NewInstancesDescribeController()
|
|
return fctl.NewCommand("describe <instance-id>",
|
|
fctl.WithShortDescription("Describe a specific workflow instance"),
|
|
fctl.WithArgs(cobra.ExactArgs(1)),
|
|
fctl.WithController[*InstancesDescribeStore](c),
|
|
)
|
|
}
|
|
|
|
func (c *InstancesDescribeController) GetStore() *InstancesDescribeStore {
|
|
return c.store
|
|
}
|
|
|
|
func (c *InstancesDescribeController) Run(cmd *cobra.Command, args []string) (fctl.Renderable, error) {
|
|
soc, err := fctl.GetStackOrganizationConfig(cmd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
client, err := fctl.NewStackClient(cmd, soc.Config, soc.Stack)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "creating stack client")
|
|
}
|
|
|
|
response, err := client.Orchestration.GetInstanceHistory(cmd.Context(), operations.GetInstanceHistoryRequest{
|
|
InstanceID: args[0],
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if response.Error != nil {
|
|
return nil, fmt.Errorf("%s: %s", response.Error.ErrorCode, response.Error.ErrorMessage)
|
|
}
|
|
|
|
if response.StatusCode >= 300 {
|
|
return nil, fmt.Errorf("unexpected status code: %d", response.StatusCode)
|
|
}
|
|
|
|
c.client = client
|
|
c.store.WorkflowInstancesHistory = response.GetWorkflowInstanceHistoryResponse.Data
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func (c *InstancesDescribeController) Render(cmd *cobra.Command, args []string) error {
|
|
|
|
for i, history := range c.store.WorkflowInstancesHistory {
|
|
if err := printStage(cmd, i, c.client, args[0], history); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func printHistoryBaseInfo(out io.Writer, name string, ind int, history shared.WorkflowInstanceHistory) {
|
|
fctl.Section.WithWriter(out).Printf("Stage %d : %s\n", ind, name)
|
|
fctl.BasicText.WithWriter(out).Printfln("Started at: %s", history.StartedAt.Format(time.RFC3339))
|
|
if history.Terminated {
|
|
fctl.BasicText.WithWriter(out).Printfln("Terminated at: %s", history.StartedAt.Format(time.RFC3339))
|
|
}
|
|
}
|
|
|
|
func stageSourceName(src *shared.StageSendSource) string {
|
|
switch {
|
|
case src.Wallet != nil:
|
|
return fmt.Sprintf("wallet '%s' (balance: %s)", src.Wallet.ID, *src.Wallet.Balance)
|
|
case src.Account != nil:
|
|
return fmt.Sprintf("account '%s' (ledger: %s)", src.Account.ID, *src.Account.Ledger)
|
|
case src.Payment != nil:
|
|
return fmt.Sprintf("payment '%s'", src.Payment.ID)
|
|
default:
|
|
return "unknown_source_type"
|
|
}
|
|
}
|
|
|
|
func stageDestinationName(dst *shared.StageSendDestination) string {
|
|
switch {
|
|
case dst.Wallet != nil:
|
|
return fmt.Sprintf("wallet '%s' (balance: %s)", dst.Wallet.ID, *dst.Wallet.Balance)
|
|
case dst.Account != nil:
|
|
return fmt.Sprintf("account '%s' (ledger: %s)", dst.Account.ID, *dst.Account.Ledger)
|
|
case dst.Payment != nil:
|
|
return dst.Payment.Psp
|
|
default:
|
|
return "unknown_source_type"
|
|
}
|
|
}
|
|
|
|
func subjectName(src shared.Subject) string {
|
|
switch {
|
|
case src.WalletSubject != nil:
|
|
return fmt.Sprintf("wallet %s (balance: %s)", src.WalletSubject.Identifier, *src.WalletSubject.Balance)
|
|
case src.LedgerAccountSubject != nil:
|
|
return fmt.Sprintf("account %s", src.LedgerAccountSubject.Identifier)
|
|
default:
|
|
return "unknown_subject_type"
|
|
}
|
|
}
|
|
|
|
func printMetadata(metadata map[string]string) []pterm.BulletListItem {
|
|
ret := make([]pterm.BulletListItem, 0)
|
|
ret = append(ret, historyItemDetails("Added metadata:"))
|
|
for k, v := range metadata {
|
|
ret = append(ret, pterm.BulletListItem{
|
|
Level: 2,
|
|
Text: fmt.Sprintf("%s: %s", k, v),
|
|
})
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func printStage(cmd *cobra.Command, i int, client *formance.Formance, id string, history shared.WorkflowInstanceHistory) error {
|
|
cyanWriter := fctl.BasicTextCyan
|
|
defaultWriter := fctl.BasicText
|
|
|
|
listItems := make([]pterm.BulletListItem, 0)
|
|
|
|
switch history.Input.Type {
|
|
case shared.StageTypeStageSend:
|
|
printHistoryBaseInfo(cmd.OutOrStdout(), "send", i, history)
|
|
cyanWriter.Printfln("Send %v %s from %s to %s", history.Input.StageSend.Amount.Amount,
|
|
history.Input.StageSend.Amount.Asset, stageSourceName(history.Input.StageSend.Source),
|
|
stageDestinationName(history.Input.StageSend.Destination))
|
|
fctl.Println()
|
|
|
|
stageResponse, err := client.Orchestration.GetInstanceStageHistory(cmd.Context(), operations.GetInstanceStageHistoryRequest{
|
|
InstanceID: id,
|
|
Number: int64(i),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if stageResponse.Error != nil {
|
|
return fmt.Errorf("%s: %s", stageResponse.Error.ErrorCode, stageResponse.Error.ErrorMessage)
|
|
}
|
|
|
|
if stageResponse.StatusCode >= 300 {
|
|
return fmt.Errorf("unexpected status code: %d", stageResponse.StatusCode)
|
|
}
|
|
|
|
for _, historyStage := range stageResponse.GetWorkflowInstanceHistoryStageResponse.Data {
|
|
switch {
|
|
case historyStage.Input.StripeTransfer != nil:
|
|
listItems = append(listItems, historyItemTitle("Send %v %s to Stripe connected account: %s",
|
|
*historyStage.Input.StripeTransfer.Amount,
|
|
*historyStage.Input.StripeTransfer.Asset,
|
|
*historyStage.Input.StripeTransfer.Destination,
|
|
))
|
|
case historyStage.Input.CreateTransaction != nil:
|
|
listItems = append(listItems, historyItemTitle("Send %v %s from account %s to account %s (ledger %s)",
|
|
historyStage.Input.CreateTransaction.Data.Postings[0].Amount,
|
|
historyStage.Input.CreateTransaction.Data.Postings[0].Asset,
|
|
historyStage.Input.CreateTransaction.Data.Postings[0].Source,
|
|
historyStage.Input.CreateTransaction.Data.Postings[0].Destination,
|
|
*historyStage.Input.CreateTransaction.Ledger,
|
|
))
|
|
if historyStage.Error == nil && historyStage.LastFailure == nil && historyStage.Terminated {
|
|
listItems = append(listItems, historyItemDetails("Created transaction: %d", historyStage.Output.CreateTransaction.Data.ID))
|
|
if historyStage.Input.CreateTransaction.Data.Reference != nil {
|
|
listItems = append(listItems, historyItemDetails("Reference: %s", *historyStage.Output.CreateTransaction.Data.Reference))
|
|
}
|
|
if len(historyStage.Input.CreateTransaction.Data.Metadata) > 0 {
|
|
listItems = append(listItems, printMetadata(historyStage.Input.CreateTransaction.Data.Metadata)...)
|
|
}
|
|
}
|
|
case historyStage.Input.ConfirmHold != nil:
|
|
listItems = append(listItems, historyItemTitle("Confirm debit hold %s", historyStage.Input.ConfirmHold.ID))
|
|
case historyStage.Input.CreditWallet != nil:
|
|
listItems = append(listItems, historyItemTitle("Credit wallet %s (balance: %s) of %v %s from %s",
|
|
*historyStage.Input.CreditWallet.ID,
|
|
*historyStage.Input.CreditWallet.Data.Balance,
|
|
historyStage.Input.CreditWallet.Data.Amount.Amount,
|
|
historyStage.Input.CreditWallet.Data.Amount.Asset,
|
|
subjectName(historyStage.Input.CreditWallet.Data.Sources[0]),
|
|
))
|
|
if historyStage.Error == nil && historyStage.LastFailure == nil && historyStage.Terminated {
|
|
if len(historyStage.Input.CreditWallet.Data.Metadata) > 0 {
|
|
listItems = append(listItems, printMetadata(historyStage.Input.CreditWallet.Data.Metadata)...)
|
|
}
|
|
}
|
|
case historyStage.Input.DebitWallet != nil:
|
|
destination := "@world"
|
|
if historyStage.Input.DebitWallet.Data.Destination != nil {
|
|
destination = subjectName(*historyStage.Input.DebitWallet.Data.Destination)
|
|
}
|
|
|
|
listItems = append(listItems, historyItemTitle("Debit wallet %s (balance: %s) of %v %s to %s",
|
|
*historyStage.Input.DebitWallet.ID,
|
|
historyStage.Input.DebitWallet.Data.Balances[0],
|
|
historyStage.Input.DebitWallet.Data.Amount.Amount,
|
|
historyStage.Input.DebitWallet.Data.Amount.Asset,
|
|
destination,
|
|
))
|
|
if historyStage.Error == nil && historyStage.LastFailure == nil && historyStage.Terminated {
|
|
if len(historyStage.Input.DebitWallet.Data.Metadata) > 0 {
|
|
listItems = append(listItems, printMetadata(historyStage.Input.DebitWallet.Data.Metadata)...)
|
|
}
|
|
}
|
|
case historyStage.Input.GetAccount != nil:
|
|
listItems = append(listItems, historyItemTitle("Read account %s of ledger %s",
|
|
historyStage.Input.GetAccount.ID,
|
|
historyStage.Input.GetAccount.Ledger,
|
|
))
|
|
case historyStage.Input.GetPayment != nil:
|
|
listItems = append(listItems, historyItemTitle("Read payment %s",
|
|
historyStage.Input.GetPayment.ID))
|
|
case historyStage.Input.GetWallet != nil:
|
|
listItems = append(listItems, historyItemTitle("Read wallet '%s'", historyStage.Input.GetWallet.ID))
|
|
case historyStage.Input.RevertTransaction != nil:
|
|
listItems = append(listItems, historyItemTitle("Revert transaction %s", historyStage.Input.RevertTransaction.ID))
|
|
if historyStage.Error == nil {
|
|
listItems = append(listItems, historyItemTitle("Created transaction: %d", historyStage.Output.RevertTransaction.Data.ID))
|
|
}
|
|
case historyStage.Input.VoidHold != nil:
|
|
listItems = append(listItems, historyItemTitle("Cancel debit hold %s", historyStage.Input.VoidHold.ID))
|
|
case historyStage.Input.ListWallets != nil:
|
|
listItems = append(listItems, historyItemTitle("List wallets"))
|
|
}
|
|
if historyStage.LastFailure != nil {
|
|
listItems = append(listItems, historyItemError(*historyStage.LastFailure))
|
|
if historyStage.NextExecution != nil {
|
|
listItems = append(listItems, historyItemError("Next try: %s", historyStage.NextExecution.Format(time.RFC3339)))
|
|
listItems = append(listItems, historyItemError("Attempt: %d", historyStage.Attempt))
|
|
}
|
|
}
|
|
if historyStage.Error != nil {
|
|
listItems = append(listItems, historyItemError(*historyStage.Error))
|
|
}
|
|
}
|
|
case shared.StageTypeStageDelay:
|
|
printHistoryBaseInfo(cmd.OutOrStdout(), "delay", i, history)
|
|
switch {
|
|
case history.Input.StageDelay.Duration != nil:
|
|
listItems = append(listItems, historyItemTitle("Pause workflow for a delay of %s", *history.Input.StageDelay.Duration))
|
|
case history.Input.StageDelay.Until != nil:
|
|
listItems = append(listItems, historyItemTitle("Pause workflow until %s", *history.Input.StageDelay.Until))
|
|
}
|
|
case shared.StageTypeStageWaitEvent:
|
|
printHistoryBaseInfo(cmd.OutOrStdout(), "wait_event", i, history)
|
|
listItems = append(listItems, historyItemTitle("Waiting event '%s'", history.Input.StageWaitEvent.Event))
|
|
if history.Error == nil {
|
|
if history.Terminated {
|
|
listItems = append(listItems, historyItemDetails("Event received!"))
|
|
} else {
|
|
listItems = append(listItems, historyItemDetails("Still waiting event..."))
|
|
}
|
|
}
|
|
default:
|
|
// Display error?
|
|
}
|
|
if history.Error != nil {
|
|
fctl.BasicTextRed.WithWriter(cmd.OutOrStdout()).Printfln("Stage terminated with error: %s", *history.Error)
|
|
}
|
|
|
|
if len(listItems) > 0 {
|
|
defaultWriter.Print("History :\n")
|
|
return pterm.DefaultBulletList.WithWriter(cmd.OutOrStdout()).WithItems(listItems).Render()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func historyItemTitle(format string, args ...any) pterm.BulletListItem {
|
|
return pterm.BulletListItem{
|
|
Level: 0,
|
|
TextStyle: fctl.StyleGreen,
|
|
Text: fmt.Sprintf(format, args...),
|
|
}
|
|
}
|
|
|
|
func historyItemDetails(format string, args ...any) pterm.BulletListItem {
|
|
return pterm.BulletListItem{
|
|
Level: 1,
|
|
Text: fmt.Sprintf(format, args...),
|
|
}
|
|
}
|
|
|
|
func historyItemError(format string, args ...any) pterm.BulletListItem {
|
|
return pterm.BulletListItem{
|
|
Level: 1,
|
|
TextStyle: fctl.StyleRed,
|
|
Text: fmt.Sprintf(format, args...),
|
|
}
|
|
}
|