github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/operators/cli/clioperator.go (about) 1 // Copyright 2024 The Inspektor Gadget authors 2 // 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 package clioperator 16 17 import ( 18 "fmt" 19 "sort" 20 "strings" 21 22 "sigs.k8s.io/yaml" 23 24 "github.com/inspektor-gadget/inspektor-gadget/pkg/datasource" 25 "github.com/inspektor-gadget/inspektor-gadget/pkg/datasource/formatters/json" 26 "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api" 27 apihelpers "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api-helpers" 28 "github.com/inspektor-gadget/inspektor-gadget/pkg/operators" 29 "github.com/inspektor-gadget/inspektor-gadget/pkg/params" 30 ) 31 32 const ( 33 // Priority is set to a high value, since this operator is used as sink and so all changes to DataSources need 34 // to have happened before the operator becomes active 35 Priority = 10000 36 37 ParamFields = "fields" 38 ParamMode = "output" 39 40 ModeJSON = "json" 41 ModeJSONPretty = "jsonpretty" 42 ModeColumns = "columns" 43 ModeYAML = "yaml" 44 ) 45 46 type cliOperator struct{} 47 48 func (o *cliOperator) Name() string { 49 return "cli" 50 } 51 52 func (o *cliOperator) Init(params *params.Params) error { 53 return nil 54 } 55 56 func (o *cliOperator) GlobalParams() api.Params { 57 return nil 58 } 59 60 func (o *cliOperator) InstanceParams() api.Params { 61 return nil 62 } 63 64 func (o *cliOperator) InstantiateDataOperator(gadgetCtx operators.GadgetContext, paramValues api.ParamValues) (operators.DataOperatorInstance, error) { 65 op := &cliOperatorInstance{ 66 mode: ModeColumns, 67 paramValues: paramValues, 68 } 69 70 return op, nil 71 } 72 73 func (o *cliOperator) Priority() int { 74 return Priority 75 } 76 77 type cliOperatorInstance struct { 78 mode string 79 paramValues api.ParamValues 80 } 81 82 func (o *cliOperatorInstance) Name() string { 83 return "cli" 84 } 85 86 func (o *cliOperatorInstance) InstanceParams() params.ParamDescs { 87 return nil 88 } 89 90 func getNamesFromFields(fields []*api.Field) []string { 91 res := make([]string, 0, len(fields)) 92 for _, f := range fields { 93 res = append(res, f.FullName) 94 } 95 return res 96 } 97 98 func (o *cliOperatorInstance) ExtraParams(gadgetCtx operators.GadgetContext) api.Params { 99 dataSources := gadgetCtx.GetDataSources() 100 101 nameDS := false 102 103 // if we have multiple DataSources, we need to prefix the list of fields with the DataSource's name 104 if len(dataSources) > 1 { 105 nameDS = true 106 } 107 108 fieldsDefaultValues := make([]string, 0, len(dataSources)) 109 fieldsDescriptions := make([]string, 0, len(dataSources)+1) 110 fieldsDescriptions = append(fieldsDescriptions, "Available data sources / fields") 111 for _, ds := range dataSources { 112 fields := ds.Fields() 113 availableFields := make([]*api.Field, 0, len(fields)) 114 defaultFields := make([]*api.Field, 0) 115 for _, f := range fields { 116 if datasource.FieldFlagUnreferenced.In(f.Flags) || 117 datasource.FieldFlagContainer.In(f.Flags) || 118 datasource.FieldFlagEmpty.In(f.Flags) { 119 continue 120 } 121 availableFields = append(availableFields, f) 122 if datasource.FieldFlagHidden.In(f.Flags) { 123 continue 124 } 125 defaultFields = append(defaultFields, f) 126 } 127 128 // Sort available fields by name 129 sort.Slice(availableFields, func(i, j int) bool { 130 return availableFields[i].FullName < availableFields[j].FullName 131 }) 132 133 // Sort default fields by order value 134 sort.SliceStable(defaultFields, func(i, j int) bool { 135 return defaultFields[i].Order < defaultFields[j].Order 136 }) 137 138 fieldsDefaultValue := strings.Join(getNamesFromFields(defaultFields), ",") 139 if nameDS { 140 fieldsDefaultValue = ds.Name() + ":" + fieldsDefaultValue 141 } 142 143 fieldsDefaultValues = append(fieldsDefaultValues, fieldsDefaultValue) 144 145 var sb strings.Builder 146 fmt.Fprintf(&sb, " %q (data source):\n", ds.Name()) 147 for _, f := range availableFields { 148 fmt.Fprintf(&sb, " %s\n", f.FullName) 149 if desc, ok := f.Annotations["description"]; ok { // TODO: const to API? 150 fmt.Fprintf(&sb, " %s\n", desc) 151 } 152 } 153 fieldsDescriptions = append(fieldsDescriptions, sb.String()) 154 } 155 156 // --fields datasource:comma,separated,fields;datasource2:comma,separated,fields 157 fields := &api.Param{ 158 Key: ParamFields, 159 DefaultValue: strings.Join(fieldsDefaultValues, ";"), 160 Description: strings.Join(fieldsDescriptions, "\n"), 161 } 162 163 mode := &api.Param{ 164 Key: ParamMode, 165 DefaultValue: ModeColumns, 166 Description: "output mode", 167 Alias: "o", 168 PossibleValues: []string{ModeJSON, ModeJSONPretty, ModeColumns, ModeYAML}, 169 } 170 171 return api.Params{fields, mode} 172 } 173 174 func (o *cliOperatorInstance) PreStart(gadgetCtx operators.GadgetContext) error { 175 params := apihelpers.ToParamDescs(o.ExtraParams(gadgetCtx)).ToParams() 176 params.CopyFromMap(o.paramValues, "") 177 178 fieldValues := strings.Split(params.Get(ParamFields).AsString(), ";") 179 fieldLookup := make(map[string]string) 180 for _, v := range fieldValues { 181 dsFieldValues := strings.SplitN(v, ":", 2) 182 var dsName string 183 dsFields := dsFieldValues[0] 184 if len(dsFieldValues) == 2 { 185 dsName = dsFieldValues[0] 186 dsFields = dsFieldValues[1] 187 } 188 fieldLookup[dsName] = dsFields 189 } 190 191 o.mode = params.Get(ParamMode).AsString() 192 193 for _, ds := range gadgetCtx.GetDataSources() { 194 gadgetCtx.Logger().Debugf("subscribing to %s", ds.Name()) 195 196 fields, hasFields := fieldLookup[ds.Name()] 197 if !hasFields { 198 fields, hasFields = fieldLookup[""] // fall back to default 199 } 200 201 switch o.mode { 202 case ModeColumns: 203 p, err := ds.Parser() 204 if err != nil { 205 gadgetCtx.Logger().Debugf("failed to get parser: %v", err) 206 continue 207 } 208 209 defCols := p.GetDefaultColumns() 210 gadgetCtx.Logger().Debugf("default fields: %s", defCols) 211 formatter := p.GetTextColumnsFormatter() 212 213 if hasFields { 214 err := formatter.SetShowColumns(strings.Split(fields, ",")) 215 if err != nil { 216 return fmt.Errorf("setting fields: %w", err) 217 } 218 } 219 220 formatter.SetEventCallback(func(s string) { 221 fmt.Print(s) 222 fmt.Print("\n") 223 }) 224 225 p.SetEventCallback(formatter.EventHandlerFunc()) 226 handler, ok := p.EventHandlerFunc().(func(data *datasource.DataTuple)) 227 if !ok { 228 gadgetCtx.Logger().Warnf("invalid data format: expected func(data *datasource.DataTuple), got %T", 229 p.EventHandlerFunc()) 230 continue 231 } 232 233 fmt.Println(formatter.FormatHeader()) 234 235 ds.Subscribe(func(ds datasource.DataSource, data datasource.Data) error { 236 handler(datasource.NewDataTuple(ds, data)) 237 return nil 238 }, Priority) 239 case ModeJSON, ModeJSONPretty, ModeYAML: 240 // var opts []json.Option 241 // if hasFields { 242 // opts = append(opts, json.WithFields(strings.Split(fields, ","))) 243 // } 244 245 jsonFormatter, err := json.New(ds, 246 // TODO: compatiblity for now: add all; remove me later on and use the commented version above 247 json.WithShowAll(true), 248 json.WithPretty(o.mode == ModeJSONPretty, " "), 249 ) 250 if err != nil { 251 return fmt.Errorf("initializing JSON formatter: %w", err) 252 } 253 254 df := func(ds datasource.DataSource, data datasource.Data) error { 255 fmt.Println(string(jsonFormatter.Marshal(data))) 256 return nil 257 } 258 259 if o.mode == ModeYAML { 260 // For the time being, this uses a slow approach to marshal to YAML, by first 261 // converting to JSON and then to YAML. This should get a dedicated formatter sooner or later. 262 df = func(ds datasource.DataSource, data datasource.Data) error { 263 yml, err := yaml.JSONToYAML(jsonFormatter.Marshal(data)) 264 if err != nil { 265 return fmt.Errorf("serializing yaml: %w", err) 266 } 267 fmt.Println("---") 268 fmt.Print(string(yml)) 269 return nil 270 } 271 } 272 ds.Subscribe(df, Priority) 273 } 274 } 275 return nil 276 } 277 278 func (o *cliOperatorInstance) Start(gadgetCtx operators.GadgetContext) error { 279 return nil 280 } 281 282 func (o *cliOperatorInstance) Stop(gadgetCtx operators.GadgetContext) error { 283 return nil 284 } 285 286 var CLIOperator = &cliOperator{}