github.com/containerd/nerdctl@v1.7.7/pkg/cmd/system/events.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package system 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "strings" 26 "text/template" 27 "time" 28 29 "github.com/containerd/containerd" 30 _ "github.com/containerd/containerd/api/events" // Register grpc event types 31 "github.com/containerd/containerd/events" 32 "github.com/containerd/log" 33 "github.com/containerd/nerdctl/pkg/api/types" 34 "github.com/containerd/nerdctl/pkg/formatter" 35 "github.com/containerd/typeurl/v2" 36 ) 37 38 // EventOut contains information about an event. 39 type EventOut struct { 40 Timestamp time.Time 41 ID string 42 Namespace string 43 Topic string 44 Status Status 45 Event string 46 } 47 48 type Status string 49 50 const ( 51 START Status = "start" 52 UNKNOWN Status = "unknown" 53 ) 54 55 var statuses = [...]Status{START, UNKNOWN} 56 57 func isStatus(status string) bool { 58 status = strings.ToLower(status) 59 60 for _, supportedStatus := range statuses { 61 if string(supportedStatus) == status { 62 return true 63 } 64 } 65 66 return false 67 } 68 69 func TopicToStatus(topic string) Status { 70 if strings.Contains(strings.ToLower(topic), string(START)) { 71 return START 72 } 73 74 return UNKNOWN 75 } 76 77 // EventFilter for filtering events 78 type EventFilter func(*EventOut) bool 79 80 // generateEventFilter is similar to Podman implementation: 81 // https://github.com/containers/podman/blob/189d862d54b3824c74bf7474ddfed6de69ec5a09/libpod/events/filters.go#L11 82 func generateEventFilter(filter, filterValue string) (func(e *EventOut) bool, error) { 83 switch strings.ToUpper(filter) { 84 case "EVENT", "STATUS": 85 return func(e *EventOut) bool { 86 if !isStatus(string(e.Status)) { 87 return false 88 } 89 90 return strings.EqualFold(string(e.Status), filterValue) 91 }, nil 92 } 93 94 return nil, fmt.Errorf("%s is an invalid or unsupported filter", filter) 95 } 96 97 // parseFilter is similar to Podman implementation: 98 // https://github.com/containers/podman/blob/189d862d54b3824c74bf7474ddfed6de69ec5a09/libpod/events/filters.go#L96 99 func parseFilter(filter string) (string, string, error) { 100 filterSplit := strings.SplitN(filter, "=", 2) 101 if len(filterSplit) != 2 { 102 return "", "", fmt.Errorf("%s is an invalid filter", filter) 103 } 104 return filterSplit[0], filterSplit[1], nil 105 } 106 107 // applyFilters is similar to Podman implementation: 108 // https://github.com/containers/podman/blob/189d862d54b3824c74bf7474ddfed6de69ec5a09/libpod/events/filters.go#L106 109 func applyFilters(event *EventOut, filterMap map[string][]EventFilter) bool { 110 for _, filters := range filterMap { 111 match := false 112 for _, filter := range filters { 113 if filter(event) { 114 match = true 115 break 116 } 117 } 118 if !match { 119 return false 120 } 121 } 122 return true 123 } 124 125 // generateEventFilters is similar to Podman implementation: 126 // https://github.com/containers/podman/blob/189d862d54b3824c74bf7474ddfed6de69ec5a09/libpod/events/filters.go#L11 127 func generateEventFilters(filters []string) (map[string][]EventFilter, error) { 128 filterMap := make(map[string][]EventFilter) 129 for _, filter := range filters { 130 key, val, err := parseFilter(filter) 131 if err != nil { 132 return nil, err 133 } 134 filterFunc, err := generateEventFilter(key, val) 135 if err != nil { 136 return nil, err 137 } 138 filterSlice := filterMap[key] 139 filterSlice = append(filterSlice, filterFunc) 140 filterMap[key] = filterSlice 141 } 142 143 return filterMap, nil 144 } 145 146 // Events is from https://github.com/containerd/containerd/blob/v1.4.3/cmd/ctr/commands/events/events.go 147 func Events(ctx context.Context, client *containerd.Client, options types.SystemEventsOptions) error { 148 eventsClient := client.EventService() 149 eventsCh, errCh := eventsClient.Subscribe(ctx) 150 var tmpl *template.Template 151 switch options.Format { 152 case "": 153 tmpl = nil 154 case "raw", "table", "wide": 155 return errors.New("unsupported format: \"raw\", \"table\", and \"wide\"") 156 default: 157 var err error 158 tmpl, err = formatter.ParseTemplate(options.Format) 159 if err != nil { 160 return err 161 } 162 } 163 filterMap, err := generateEventFilters(options.Filters) 164 if err != nil { 165 return err 166 } 167 for { 168 var e *events.Envelope 169 select { 170 case e = <-eventsCh: 171 case err := <-errCh: 172 return err 173 } 174 if e != nil { 175 var out []byte 176 var id string 177 if e.Event != nil { 178 v, err := typeurl.UnmarshalAny(e.Event) 179 if err != nil { 180 log.G(ctx).WithError(err).Warn("cannot unmarshal an event from Any") 181 continue 182 } 183 out, err = json.Marshal(v) 184 if err != nil { 185 log.G(ctx).WithError(err).Warn("cannot marshal Any into JSON") 186 continue 187 } 188 } 189 var data map[string]interface{} 190 err := json.Unmarshal(out, &data) 191 if err != nil { 192 log.G(ctx).WithError(err).Warn("cannot marshal Any into JSON") 193 } else { 194 _, ok := data["container_id"] 195 if ok { 196 id = data["container_id"].(string) 197 } 198 } 199 200 eOut := EventOut{e.Timestamp, id, e.Namespace, e.Topic, TopicToStatus(e.Topic), string(out)} 201 match := applyFilters(&eOut, filterMap) 202 if match { 203 if tmpl != nil { 204 var b bytes.Buffer 205 if err := tmpl.Execute(&b, eOut); err != nil { 206 return err 207 } 208 if _, err := fmt.Fprintln(options.Stdout, b.String()+"\n"); err != nil { 209 return err 210 } 211 } else { 212 if _, err := fmt.Fprintln( 213 options.Stdout, 214 e.Timestamp, 215 e.Namespace, 216 e.Topic, 217 string(out), 218 ); err != nil { 219 return err 220 } 221 } 222 } 223 224 } 225 } 226 }