github.com/SamarSidharth/kpt@v0.0.0-20231122062228-c7d747ae3ace/commands/alpha/rpkg/get/command.go (about) 1 // Copyright 2022 The kpt 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 get 16 17 import ( 18 "context" 19 "fmt" 20 "strings" 21 22 "github.com/GoogleContainerTools/kpt/internal/docs/generated/rpkgdocs" 23 "github.com/GoogleContainerTools/kpt/internal/errors" 24 "github.com/GoogleContainerTools/kpt/internal/options" 25 "github.com/GoogleContainerTools/kpt/internal/util/porch" 26 "github.com/spf13/cobra" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 "k8s.io/apimachinery/pkg/fields" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 "k8s.io/cli-runtime/pkg/genericclioptions" 33 "k8s.io/cli-runtime/pkg/printers" 34 "k8s.io/client-go/rest" 35 "k8s.io/klog/v2" 36 "k8s.io/kubectl/pkg/cmd/get" 37 ) 38 39 const ( 40 command = "cmdrpkgget" 41 ) 42 43 func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner { 44 r := &runner{ 45 ctx: ctx, 46 getFlags: options.Get{ConfigFlags: rcg}, 47 printFlags: get.NewGetPrintFlags(), 48 } 49 cmd := &cobra.Command{ 50 Use: "get", 51 Aliases: []string{"list"}, 52 SuggestFor: []string{}, 53 Short: rpkgdocs.GetShort, 54 Long: rpkgdocs.GetShort + "\n" + rpkgdocs.GetLong, 55 Example: rpkgdocs.GetExamples, 56 PreRunE: r.preRunE, 57 RunE: r.runE, 58 Hidden: porch.HidePorchCommands, 59 } 60 r.Command = cmd 61 62 // Create flags 63 cmd.Flags().StringVar(&r.packageName, "name", "", "Name of the packages to get. Any package whose name contains this value will be included in the results.") 64 cmd.Flags().StringVar(&r.revision, "revision", "", "Revision of the packages to get. Any package whose revision matches this value will be included in the results.") 65 cmd.Flags().StringVar(&r.workspace, "workspace", "", 66 "WorkspaceName of the packages to get. Any package whose workspaceName matches this value will be included in the results.") 67 68 r.getFlags.AddFlags(cmd) 69 r.printFlags.AddFlags(cmd) 70 return r 71 } 72 73 func NewCommand(ctx context.Context, rcg *genericclioptions.ConfigFlags) *cobra.Command { 74 return newRunner(ctx, rcg).Command 75 } 76 77 type runner struct { 78 ctx context.Context 79 getFlags options.Get 80 Command *cobra.Command 81 82 // Flags 83 packageName string 84 revision string 85 workspace string 86 printFlags *get.PrintFlags 87 88 requestTable bool 89 } 90 91 func (r *runner) preRunE(cmd *cobra.Command, _ []string) error { 92 // Print the namespace if we're spanning namespaces 93 if r.getFlags.AllNamespaces { 94 r.printFlags.HumanReadableFlags.WithNamespace = true 95 } 96 97 outputOption := cmd.Flags().Lookup("output").Value.String() 98 if strings.Contains(outputOption, "custom-columns") || outputOption == "yaml" || strings.Contains(outputOption, "json") { 99 r.requestTable = false 100 } else { 101 r.requestTable = true 102 } 103 return nil 104 } 105 106 func (r *runner) runE(cmd *cobra.Command, args []string) error { 107 const op errors.Op = command + ".runE" 108 109 var objs []runtime.Object 110 b, err := r.getFlags.ResourceBuilder() 111 if err != nil { 112 return err 113 } 114 115 if r.requestTable { 116 scheme := runtime.NewScheme() 117 // Accept PartialObjectMetadata and Table 118 if err := metav1.AddMetaToScheme(scheme); err != nil { 119 return fmt.Errorf("error building runtime.Scheme: %w", err) 120 } 121 b = b.WithScheme(scheme, schema.GroupVersion{Version: "v1"}) 122 } else { 123 // We want to print the server version, not whatever version we happen to have compiled in 124 b = b.Unstructured() 125 } 126 127 useSelectors := true 128 if len(args) > 0 { 129 b = b.ResourceNames("packagerevisions", args...) 130 // We can't pass selectors here, get an error "Error: selectors and the all flag cannot be used when passing resource/name arguments" 131 // TODO: cli-utils bug? I think there is a metadata.name field selector (used for single object watch) 132 useSelectors = false 133 } else { 134 b = b.ResourceTypes("packagerevisions") 135 } 136 137 if useSelectors { 138 fieldSelector := fields.Everything() 139 if r.revision != "" { 140 fieldSelector = fields.OneTermEqualSelector("spec.revision", r.revision) 141 } 142 if r.workspace != "" { 143 fieldSelector = fields.OneTermEqualSelector("spec.workspaceName", r.workspace) 144 } 145 if r.packageName != "" { 146 fieldSelector = fields.OneTermEqualSelector("spec.packageName", r.packageName) 147 } 148 if s := fieldSelector.String(); s != "" { 149 b = b.FieldSelectorParam(s) 150 } else { 151 b = b.SelectAllParam(true) 152 } 153 } 154 155 b = b.ContinueOnError(). 156 Latest(). 157 Flatten() 158 159 if r.requestTable { 160 b = b.TransformRequests(func(req *rest.Request) { 161 req.SetHeader("Accept", strings.Join([]string{ 162 "application/json;as=Table;g=meta.k8s.io;v=v1", 163 "application/json", 164 }, ",")) 165 }) 166 } 167 168 res := b.Do() 169 if err := res.Err(); err != nil { 170 return errors.E(op, err) 171 } 172 173 infos, err := res.Infos() 174 if err != nil { 175 return errors.E(op, err) 176 } 177 178 // Decode json objects in tables (likely PartialObjectMetadata) 179 for _, i := range infos { 180 if table, ok := i.Object.(*metav1.Table); ok { 181 for i := range table.Rows { 182 row := &table.Rows[i] 183 if row.Object.Object == nil && row.Object.Raw != nil { 184 u := &unstructured.Unstructured{} 185 if err := u.UnmarshalJSON(row.Object.Raw); err != nil { 186 klog.Warningf("error parsing raw object: %v", err) 187 } 188 row.Object.Object = u 189 } 190 } 191 } 192 } 193 194 // Apply any filters we couldn't pass down as field selectors 195 for _, i := range infos { 196 switch obj := i.Object.(type) { 197 case *unstructured.Unstructured: 198 match, err := r.packageRevisionMatches(obj) 199 if err != nil { 200 return errors.E(op, err) 201 } 202 if match { 203 objs = append(objs, obj) 204 } 205 case *metav1.Table: 206 // Technically we should have applied this as a field-selector, so this might not be necessary 207 if err := r.filterTableRows(obj); err != nil { 208 return err 209 } 210 objs = append(objs, obj) 211 default: 212 return errors.E(op, fmt.Sprintf("Unrecognized response %T", obj)) 213 } 214 } 215 216 printer, err := r.printFlags.ToPrinter() 217 if err != nil { 218 return errors.E(op, err) 219 } 220 221 w := printers.GetNewTabWriter(cmd.OutOrStdout()) 222 for _, obj := range objs { 223 if err := printer.PrintObj(obj, w); err != nil { 224 return errors.E(op, err) 225 } 226 } 227 if err := w.Flush(); err != nil { 228 return errors.E(op, err) 229 } 230 231 return nil 232 } 233 234 func (r *runner) packageRevisionMatches(o *unstructured.Unstructured) (bool, error) { 235 packageName, _, err := unstructured.NestedString(o.Object, "spec", "packageName") 236 if err != nil { 237 return false, err 238 } 239 revision, _, err := unstructured.NestedString(o.Object, "spec", "revision") 240 if err != nil { 241 return false, err 242 } 243 workspace, _, err := unstructured.NestedString(o.Object, "spec", "workspaceName") 244 if err != nil { 245 return false, err 246 } 247 if r.packageName != "" && r.packageName != packageName { 248 return false, nil 249 } 250 if r.revision != "" && r.revision != revision { 251 return false, nil 252 } 253 if r.workspace != "" && r.workspace != workspace { 254 return false, nil 255 } 256 return true, nil 257 } 258 259 func findColumn(cols []metav1.TableColumnDefinition, name string) int { 260 for i := range cols { 261 if cols[i].Name == name { 262 return i 263 } 264 } 265 return -1 266 } 267 268 func getStringCell(cells []interface{}, col int) (string, bool) { 269 if col < 0 { 270 return "", false 271 } 272 s, ok := cells[col].(string) 273 return s, ok 274 } 275 276 func (r *runner) filterTableRows(table *metav1.Table) error { 277 filtered := make([]metav1.TableRow, 0, len(table.Rows)) 278 packageNameCol := findColumn(table.ColumnDefinitions, "Package") 279 revisionCol := findColumn(table.ColumnDefinitions, "Revision") 280 workspaceCol := findColumn(table.ColumnDefinitions, "WorkspaceName") 281 282 for i := range table.Rows { 283 row := &table.Rows[i] 284 285 if packageName, ok := getStringCell(row.Cells, packageNameCol); ok { 286 if r.packageName != "" && r.packageName != packageName { 287 continue 288 } 289 } 290 if revision, ok := getStringCell(row.Cells, revisionCol); ok { 291 if r.revision != "" && r.revision != revision { 292 continue 293 } 294 } 295 if workspace, ok := getStringCell(row.Cells, workspaceCol); ok { 296 if r.workspace != "" && r.workspace != workspace { 297 continue 298 } 299 } 300 301 // Row matches 302 filtered = append(filtered, *row) 303 } 304 table.Rows = filtered 305 return nil 306 }