github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/subtool.go (about)

     1  /*
     2   * Copyright 2018-2019 The NATS Authors
     3   * Licensed under the Apache License, Version 2.0 (the "License");
     4   * you may not use this file except in compliance with the License.
     5   * You may obtain a copy of the License at
     6   *
     7   * http://www.apache.org/licenses/LICENSE-2.0
     8   *
     9   * Unless required by applicable law or agreed to in writing, software
    10   * distributed under the License is distributed on an "AS IS" BASIS,
    11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12   * See the License for the specific language governing permissions and
    13   * limitations under the License.
    14   */
    15  
    16  package cmd
    17  
    18  import (
    19  	"errors"
    20  	"fmt"
    21  	"os"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/nats-io/nsc/v2/cmd/store"
    26  
    27  	nats "github.com/nats-io/nats.go"
    28  
    29  	"github.com/spf13/cobra"
    30  )
    31  
    32  func createSubCmd() *cobra.Command {
    33  	var params SubParams
    34  	var cmd = &cobra.Command{
    35  		Use:     "sub",
    36  		Short:   "Subscribe to a subject on a NATS account",
    37  		Example: "nsc tool sub <subject>\nnsc tool --queue <name> subject",
    38  		Args:    cobra.MinimumNArgs(1),
    39  		RunE: func(cmd *cobra.Command, args []string) error {
    40  			return RunAction(cmd, args, &params)
    41  		},
    42  	}
    43  	cmd.Flags().StringVarP(&params.queue, "queue", "q", "", "subscription queue name")
    44  	cmd.Flags().IntVarP(&params.maxMessages, "max-messages", "", -1, "max messages")
    45  	cmd.Flags().BoolVarP(&encryptFlag, "encrypt", "E", false, "encrypted payload")
    46  	cmd.Flags().MarkHidden("max-messages")
    47  	cmd.Flags().MarkHidden("decrypt")
    48  
    49  	params.BindFlags(cmd)
    50  	return cmd
    51  }
    52  
    53  func init() {
    54  	toolCmd.AddCommand(createSubCmd())
    55  	hidden := createSubCmd()
    56  	hidden.Hidden = true
    57  	hidden.Example = "nsc sub <subject>\nnsc --queue <name> subject"
    58  	GetRootCmd().AddCommand(hidden)
    59  }
    60  
    61  type SubParams struct {
    62  	AccountUserContextParams
    63  	credsPath   string
    64  	natsURLs    []string
    65  	queue       string
    66  	maxMessages int
    67  }
    68  
    69  func (p *SubParams) SetDefaults(ctx ActionCtx) error {
    70  	return p.AccountUserContextParams.SetDefaults(ctx)
    71  }
    72  
    73  func (p *SubParams) PreInteractive(ctx ActionCtx) error {
    74  	return p.AccountUserContextParams.Edit(ctx)
    75  }
    76  
    77  func (p *SubParams) Load(ctx ActionCtx) error {
    78  	p.credsPath = ctx.StoreCtx().KeyStore.CalcUserCredsPath(p.AccountContextParams.Name, p.UserContextParams.Name)
    79  	if natsURLFlag != "" {
    80  		p.natsURLs = []string{natsURLFlag}
    81  		return nil
    82  	}
    83  
    84  	oc, err := ctx.StoreCtx().Store.ReadOperatorClaim()
    85  	if err != nil {
    86  		return err
    87  	}
    88  	p.natsURLs = oc.OperatorServiceURLs
    89  	return nil
    90  }
    91  
    92  func (p *SubParams) PostInteractive(ctx ActionCtx) error {
    93  	return nil
    94  }
    95  
    96  func (p *SubParams) Validate(ctx ActionCtx) error {
    97  	if err := p.AccountUserContextParams.Validate(ctx); err != nil {
    98  		return err
    99  	}
   100  
   101  	if p.maxMessages == 0 {
   102  		return errors.New("max-messages must be greater than zero")
   103  	}
   104  
   105  	if p.credsPath == "" {
   106  		return fmt.Errorf("a creds file for account %q/%q was not found", p.AccountContextParams.Name, p.UserContextParams.Name)
   107  	}
   108  	_, err := os.Stat(p.credsPath)
   109  	if os.IsNotExist(err) {
   110  		return err
   111  	}
   112  	if len(p.natsURLs) == 0 {
   113  		return fmt.Errorf("operator %q doesn't have operator_service_urls set", ctx.StoreCtx().Operator.Name)
   114  	}
   115  	return nil
   116  }
   117  
   118  func (p *SubParams) Run(ctx ActionCtx) (store.Status, error) {
   119  	nc, err := nats.Connect(strings.Join(p.natsURLs, ", "),
   120  		createDefaultToolOptions("nsc_sub", ctx, nats.UserCredentials(p.credsPath))...)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  	defer nc.Close()
   125  
   126  	subj := ctx.Args()[0]
   127  	// we are doing sync subs because we want the cli to cleanup properly
   128  	// when the command returns
   129  	var sub *nats.Subscription
   130  	if p.queue != "" {
   131  		sub, err = nc.QueueSubscribeSync(subj, p.queue)
   132  		if err != nil {
   133  			return nil, err
   134  		}
   135  	} else {
   136  		sub, err = nc.SubscribeSync(subj)
   137  		if err != nil {
   138  			return nil, err
   139  		}
   140  	}
   141  	if p.maxMessages > 0 {
   142  		if err := sub.AutoUnsubscribe(p.maxMessages); err != nil {
   143  			return nil, err
   144  		}
   145  		ctx.CurrentCmd().Printf("Listening on [%s] for %d messages\n", subj, p.maxMessages)
   146  	} else {
   147  		ctx.CurrentCmd().Printf("Listening on [%s]\n", subj)
   148  	}
   149  
   150  	if err := nc.Flush(); err != nil {
   151  		return nil, err
   152  	}
   153  	if err := nc.LastError(); err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	var seed string
   158  	if encryptFlag {
   159  		// cannot fail if we are here
   160  		seed, err = ctx.StoreCtx().KeyStore.GetSeed(ctx.StoreCtx().Account.PublicKey)
   161  		if err != nil {
   162  			return nil, fmt.Errorf("unable to get the account private key to encrypt/decrypt the payload: %v", err)
   163  		}
   164  	}
   165  
   166  	i := 0
   167  	for {
   168  		msg, err := sub.NextMsg(10 * time.Second)
   169  		if err == nats.ErrTimeout {
   170  			continue
   171  		}
   172  		if err == nats.ErrMaxMessages {
   173  			break
   174  		}
   175  		if err == nats.ErrConnectionClosed {
   176  			break
   177  		}
   178  		if err != nil {
   179  			return nil, err
   180  		}
   181  
   182  		i++
   183  		if encryptFlag {
   184  			msg = maybeDecryptMessage(seed, msg)
   185  		}
   186  		ctx.CurrentCmd().Printf("[#%d] received on [%s]: '%s'\n", i, msg.Subject, string(msg.Data))
   187  	}
   188  
   189  	return nil, nil
   190  }
   191  
   192  func maybeDecryptMessage(seed string, msg *nats.Msg) *nats.Msg {
   193  	var dmsg nats.Msg
   194  	// last part of the subject will be encrypted
   195  	tokens := strings.Split(msg.Subject, ".")
   196  	k := tokens[len(tokens)-1]
   197  	kk, err := Decrypt(seed, []byte(k))
   198  	if err != nil {
   199  		dmsg.Subject = msg.Subject
   200  	} else {
   201  		tokens[len(tokens)-1] = string(kk)
   202  		dmsg.Subject = strings.Join(tokens, ".")
   203  	}
   204  
   205  	dmsg.Data, err = Decrypt(seed, msg.Data)
   206  	if err != nil {
   207  		dmsg.Data = msg.Data
   208  	}
   209  	return &dmsg
   210  }