github.com/kbehouse/nsc@v0.0.6/cmd/replytool.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/kbehouse/nsc/cmd/store"
    26  
    27  	nats "github.com/nats-io/nats.go"
    28  	"github.com/spf13/cobra"
    29  )
    30  
    31  func createReplyCmd() *cobra.Command {
    32  	var params RepParams
    33  	var cmd = &cobra.Command{
    34  		Use:     "reply",
    35  		Short:   "Reply to requests on a subject on a NATS account",
    36  		Example: "nsc tool reply <subject> <opt_reply>\nnsc tool reply --queue <name> subject <opt_reply>",
    37  		Args:    cobra.MinimumNArgs(1),
    38  		RunE: func(cmd *cobra.Command, args []string) error {
    39  			return RunAction(cmd, args, &params)
    40  		},
    41  	}
    42  	cmd.Flags().StringVarP(&params.queue, "queue", "q", "", "reply queue name")
    43  	cmd.Flags().IntVarP(&params.maxMessages, "max-messages", "", -1, "max messages")
    44  	params.BindFlags(cmd)
    45  	return cmd
    46  }
    47  
    48  func init() {
    49  	toolCmd.AddCommand(createReplyCmd())
    50  	hidden := createReplyCmd()
    51  	hidden.Hidden = true
    52  	hidden.Example = "nsc reply <subject> <opt_reply>\nnsc tool reply --queue <name> subject <opt_reply>"
    53  	GetRootCmd().AddCommand(hidden)
    54  }
    55  
    56  type RepParams struct {
    57  	AccountUserContextParams
    58  	credsPath   string
    59  	natsURLs    []string
    60  	queue       string
    61  	maxMessages int
    62  }
    63  
    64  func (p *RepParams) SetDefaults(ctx ActionCtx) error {
    65  	return p.AccountUserContextParams.SetDefaults(ctx)
    66  }
    67  
    68  func (p *RepParams) PreInteractive(ctx ActionCtx) error {
    69  	return p.AccountUserContextParams.Edit(ctx)
    70  }
    71  
    72  func (p *RepParams) Load(ctx ActionCtx) error {
    73  	p.credsPath = ctx.StoreCtx().KeyStore.CalcUserCredsPath(p.AccountContextParams.Name, p.UserContextParams.Name)
    74  	if natsURLFlag != "" {
    75  		p.natsURLs = []string{natsURLFlag}
    76  		return nil
    77  	}
    78  
    79  	oc, err := ctx.StoreCtx().Store.ReadOperatorClaim()
    80  	if err != nil {
    81  		return err
    82  	}
    83  	p.natsURLs = oc.OperatorServiceURLs
    84  	return nil
    85  }
    86  
    87  func (p *RepParams) PostInteractive(ctx ActionCtx) error {
    88  	return nil
    89  }
    90  
    91  func (p *RepParams) Validate(ctx ActionCtx) error {
    92  	if err := p.AccountUserContextParams.Validate(ctx); err != nil {
    93  		return err
    94  	}
    95  
    96  	if p.maxMessages == 0 {
    97  		return errors.New("max-messages must be greater than zero")
    98  	}
    99  
   100  	if p.credsPath == "" {
   101  		return fmt.Errorf("a creds file for account %q/%q was not found", p.AccountContextParams.Name, p.UserContextParams.Name)
   102  	}
   103  	_, err := os.Stat(p.credsPath)
   104  	if os.IsNotExist(err) {
   105  		return err
   106  	}
   107  	if len(p.natsURLs) == 0 {
   108  		return fmt.Errorf("operator %q doesn't have operator_service_urls set", ctx.StoreCtx().Operator.Name)
   109  	}
   110  	return nil
   111  }
   112  
   113  func (p *RepParams) Run(ctx ActionCtx) (store.Status, error) {
   114  	nc, err := nats.Connect(strings.Join(p.natsURLs, ","),
   115  		createDefaultToolOptions("nsc_reply", ctx, nats.UserCredentials(p.credsPath))...)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	defer nc.Close()
   120  
   121  	subj := ctx.Args()[0]
   122  	var resp []byte
   123  	if len(ctx.Args()) > 1 {
   124  		resp = []byte(ctx.Args()[1])
   125  	}
   126  	// we are doing sync subs because we want the cli to cleanup properly
   127  	// when the command returns
   128  	var sub *nats.Subscription
   129  	if p.queue != "" {
   130  		sub, err = nc.QueueSubscribeSync(subj, p.queue)
   131  		if err != nil {
   132  			return nil, err
   133  		}
   134  	} else {
   135  		sub, err = nc.SubscribeSync(subj)
   136  		if err != nil {
   137  			return nil, err
   138  		}
   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  
   154  	i := 0
   155  	for {
   156  		msg, err := sub.NextMsg(10 * time.Second)
   157  		if err == nats.ErrTimeout {
   158  			continue
   159  		}
   160  		if err == nats.ErrMaxMessages {
   161  			break
   162  		}
   163  		if err == nats.ErrConnectionClosed {
   164  			break
   165  		}
   166  		if err != nil {
   167  			return nil, err
   168  		}
   169  
   170  		i++
   171  		payload := msg.Data
   172  		if resp != nil {
   173  			payload = resp
   174  		}
   175  		if msg.Reply == "" {
   176  			ctx.CurrentCmd().Printf("[#%d] ignoring request on [%s]: no reply subject\n", i, msg.Subject)
   177  			continue
   178  		}
   179  		ctx.CurrentCmd().Printf("[#%d] received on [%s]: '%s'\n", i, msg.Subject, string(msg.Data))
   180  
   181  		if err := nc.Publish(msg.Reply, []byte(payload)); err != nil {
   182  			ctx.CurrentCmd().Printf("[#%d] error responding: '%v'\n", i, err)
   183  			return nil, err
   184  		}
   185  	}
   186  
   187  	return nil, nil
   188  }