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, ¶ms) 40 }, 41 } 42 cmd.Flags().StringVarP(¶ms.queue, "queue", "q", "", "reply queue name") 43 cmd.Flags().IntVarP(¶ms.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 }