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