github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/watch-main.go (about) 1 // Copyright (c) 2015-2022 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "context" 22 "fmt" 23 "strings" 24 "sync" 25 26 humanize "github.com/dustin/go-humanize" 27 "github.com/fatih/color" 28 "github.com/minio/cli" 29 json "github.com/minio/colorjson" 30 "github.com/minio/mc/pkg/probe" 31 "github.com/minio/minio-go/v7/pkg/notification" 32 "github.com/minio/pkg/v2/console" 33 ) 34 35 var watchFlags = []cli.Flag{ 36 cli.StringFlag{ 37 Name: "events", 38 Value: "put,delete,get", 39 Usage: "filter specific types of events; defaults to all events by default", 40 }, 41 cli.StringFlag{ 42 Name: "prefix", 43 Usage: "filter events for a prefix", 44 }, 45 cli.StringFlag{ 46 Name: "suffix", 47 Usage: "filter events for a suffix", 48 }, 49 cli.BoolFlag{ 50 Name: "recursive", 51 Usage: "recursively watch for events", 52 }, 53 } 54 55 var watchCmd = cli.Command{ 56 Name: "watch", 57 Usage: "listen for object notification events", 58 Action: mainWatch, 59 OnUsageError: onUsageError, 60 Before: setGlobalsFromContext, 61 Flags: append(watchFlags, globalFlags...), 62 CustomHelpTemplate: `NAME: 63 {{.HelpName}} - {{.Usage}} 64 65 USAGE: 66 {{.HelpName}} [FLAGS] TARGET 67 68 FLAGS: 69 {{range .VisibleFlags}}{{.}} 70 {{end}} 71 EXAMPLES: 72 1. Watch new S3 operations on a MinIO server 73 {{.Prompt}} {{.HelpName}} play/testbucket 74 75 2. Watch new events for a specific prefix "output/" on MinIO server. 76 {{.Prompt}} {{.HelpName}} --prefix "output/" play/testbucket 77 78 3. Watch new events for a specific suffix ".jpg" on MinIO server. 79 {{.Prompt}} {{.HelpName}} --suffix ".jpg" play/testbucket 80 81 4. Watch new events on a specific prefix and suffix on MinIO server. 82 {{.Prompt}} {{.HelpName}} --suffix ".jpg" --prefix "photos/" play/testbucket 83 84 5. Site level watch (except new buckets created after running this command) 85 {{.Prompt}} {{.HelpName}} play/ 86 87 6. Watch for events on local directory. 88 {{.Prompt}} {{.HelpName}} /usr/share 89 `, 90 } 91 92 // checkWatchSyntax - validate all the passed arguments 93 func checkWatchSyntax(ctx *cli.Context) { 94 if len(ctx.Args()) != 1 { 95 showCommandHelpAndExit(ctx, 1) // last argument is exit code 96 } 97 } 98 99 // watchMessage container to hold one event notification 100 type watchMessage struct { 101 Status string `json:"status"` 102 Event struct { 103 Time string `json:"time"` 104 Size int64 `json:"size"` 105 Path string `json:"path"` 106 Type notification.EventType `json:"type"` 107 } `json:"events"` 108 Source struct { 109 Host string `json:"host,omitempty"` 110 Port string `json:"port,omitempty"` 111 UserAgent string `json:"userAgent,omitempty"` 112 } `json:"source,omitempty"` 113 } 114 115 func (u watchMessage) JSON() string { 116 u.Status = "success" 117 watchMessageJSONBytes, e := json.MarshalIndent(u, "", " ") 118 fatalIf(probe.NewError(e), "Unable to marshal into JSON.") 119 return string(watchMessageJSONBytes) 120 } 121 122 func (u watchMessage) String() string { 123 msg := console.Colorize("Time", fmt.Sprintf("[%s] ", u.Event.Time)) 124 if strings.HasPrefix(string(u.Event.Type), "s3:ObjectCreated:") { 125 msg += console.Colorize("Size", fmt.Sprintf("%6s ", humanize.IBytes(uint64(u.Event.Size)))) 126 } else { 127 msg += fmt.Sprintf("%6s ", "") 128 } 129 msg += console.Colorize("EventType", fmt.Sprintf("%s ", u.Event.Type)) 130 msg += console.Colorize("ObjectName", u.Event.Path) 131 return msg 132 } 133 134 func mainWatch(cliCtx *cli.Context) error { 135 console.SetColor("Time", color.New(color.FgGreen)) 136 console.SetColor("Size", color.New(color.FgYellow)) 137 console.SetColor("EventType", color.New(color.FgCyan, color.Bold)) 138 console.SetColor("ObjectName", color.New(color.Bold)) 139 140 checkWatchSyntax(cliCtx) 141 142 args := cliCtx.Args() 143 path := args[0] 144 145 prefix := cliCtx.String("prefix") 146 suffix := cliCtx.String("suffix") 147 events := strings.Split(cliCtx.String("events"), ",") 148 recursive := cliCtx.Bool("recursive") 149 150 s3Client, pErr := newClient(path) 151 if pErr != nil { 152 fatalIf(pErr.Trace(), "Unable to parse the provided url.") 153 } 154 155 options := WatchOptions{ 156 Recursive: recursive, 157 Events: events, 158 Prefix: prefix, 159 Suffix: suffix, 160 } 161 162 ctx, cancelWatch := context.WithCancel(globalContext) 163 defer cancelWatch() 164 165 // Start watching on events 166 wo, err := s3Client.Watch(ctx, options) 167 fatalIf(err, "Unable to watch on the specified bucket.") 168 169 // Initialize.. waitgroup to track the go-routine. 170 var wg sync.WaitGroup 171 172 // Increment wait group to wait subsequent routine. 173 wg.Add(1) 174 175 // Start routine to watching on events. 176 go func() { 177 defer wg.Done() 178 179 // Wait for all events. 180 for { 181 select { 182 case <-globalContext.Done(): 183 // Signal received we are done. 184 close(wo.DoneChan) 185 return 186 case events, ok := <-wo.Events(): 187 if !ok { 188 return 189 } 190 for _, event := range events { 191 msg := watchMessage{} 192 msg.Event.Path = event.Path 193 msg.Event.Size = event.Size 194 msg.Event.Time = event.Time 195 msg.Event.Type = event.Type 196 msg.Source.Host = event.Host 197 msg.Source.Port = event.Port 198 msg.Source.UserAgent = event.UserAgent 199 printMsg(msg) 200 } 201 case err, ok := <-wo.Errors(): 202 if !ok { 203 return 204 } 205 if err != nil { 206 errorIf(err, "Unable to watch for events.") 207 return 208 } 209 } 210 } 211 }() 212 213 // Wait on the routine to be finished or exit. 214 wg.Wait() 215 216 return nil 217 }