github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/commands/alpha/rpkg/get/command.go (about) 1 // Copyright 2022 Google LLC 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 66 r.getFlags.AddFlags(cmd) 67 r.printFlags.AddFlags(cmd) 68 return r 69 } 70 71 func NewCommand(ctx context.Context, rcg *genericclioptions.ConfigFlags) *cobra.Command { 72 return newRunner(ctx, rcg).Command 73 } 74 75 type runner struct { 76 ctx context.Context 77 getFlags options.Get 78 Command *cobra.Command 79 80 // Flags 81 packageName string 82 revision string 83 printFlags *get.PrintFlags 84 85 requestTable bool 86 } 87 88 func (r *runner) preRunE(cmd *cobra.Command, args []string) error { 89 // Print the namespace if we're spanning namespaces 90 if r.getFlags.AllNamespaces { 91 r.printFlags.HumanReadableFlags.WithNamespace = true 92 } 93 94 outputOption := cmd.Flags().Lookup("output").Value.String() 95 if strings.Contains(outputOption, "custom-columns") || outputOption == "yaml" || strings.Contains(outputOption, "json") { 96 r.requestTable = false 97 } else { 98 r.requestTable = true 99 } 100 return nil 101 } 102 103 func (r *runner) runE(cmd *cobra.Command, args []string) error { 104 const op errors.Op = command + ".runE" 105 106 var objs []runtime.Object 107 b, err := r.getFlags.ResourceBuilder() 108 if err != nil { 109 return err 110 } 111 112 if r.requestTable { 113 scheme := runtime.NewScheme() 114 // Accept PartialObjectMetadata and Table 115 if err := metav1.AddMetaToScheme(scheme); err != nil { 116 return fmt.Errorf("error building runtime.Scheme: %w", err) 117 } 118 b = b.WithScheme(scheme, schema.GroupVersion{Version: "v1"}) 119 } else { 120 // We want to print the server version, not whatever version we happen to have compiled in 121 b = b.Unstructured() 122 } 123 124 useSelectors := true 125 if len(args) > 0 { 126 b = b.ResourceNames("packagerevisions", args...) 127 // We can't pass selectors here, get an error "Error: selectors and the all flag cannot be used when passing resource/name arguments" 128 // TODO: cli-utils bug? I think there is a metadata.name field selector (used for single object watch) 129 useSelectors = false 130 } else { 131 b = b.ResourceTypes("packagerevisions") 132 } 133 134 if useSelectors { 135 fieldSelector := fields.Everything() 136 if r.revision != "" { 137 fieldSelector = fields.OneTermEqualSelector("spec.revision", r.revision) 138 } 139 if r.packageName != "" { 140 fieldSelector = fields.OneTermEqualSelector("spec.packageName", r.packageName) 141 } 142 if s := fieldSelector.String(); s != "" { 143 b = b.FieldSelectorParam(s) 144 } else { 145 b = b.SelectAllParam(true) 146 } 147 } 148 149 b = b.ContinueOnError(). 150 Latest(). 151 Flatten() 152 153 if r.requestTable { 154 b = b.TransformRequests(func(req *rest.Request) { 155 req.SetHeader("Accept", strings.Join([]string{ 156 "application/json;as=Table;g=meta.k8s.io;v=v1", 157 "application/json", 158 }, ",")) 159 }) 160 } 161 162 res := b.Do() 163 if err := res.Err(); err != nil { 164 return errors.E(op, err) 165 } 166 167 infos, err := res.Infos() 168 if err != nil { 169 return errors.E(op, err) 170 } 171 172 // Decode json objects in tables (likely PartialObjectMetadata) 173 for _, i := range infos { 174 if table, ok := i.Object.(*metav1.Table); ok { 175 for i := range table.Rows { 176 row := &table.Rows[i] 177 if row.Object.Object == nil && row.Object.Raw != nil { 178 u := &unstructured.Unstructured{} 179 if err := u.UnmarshalJSON(row.Object.Raw); err != nil { 180 klog.Warningf("error parsing raw object: %v", err) 181 } 182 row.Object.Object = u 183 } 184 } 185 } 186 } 187 188 // Apply any filters we couldn't pass down as field selectors 189 for _, i := range infos { 190 switch obj := i.Object.(type) { 191 case *unstructured.Unstructured: 192 match, err := r.packageRevisionMatches(obj) 193 if err != nil { 194 return errors.E(op, err) 195 } 196 if match { 197 objs = append(objs, obj) 198 } 199 case *metav1.Table: 200 // Technically we should have applied this as a field-selector, so this might not be necessary 201 if err := r.filterTableRows(obj); err != nil { 202 return err 203 } 204 objs = append(objs, obj) 205 default: 206 return errors.E(op, fmt.Sprintf("Unrecognized response %T", obj)) 207 } 208 } 209 210 printer, err := r.printFlags.ToPrinter() 211 if err != nil { 212 return errors.E(op, err) 213 } 214 215 w := printers.GetNewTabWriter(cmd.OutOrStdout()) 216 for _, obj := range objs { 217 if err := printer.PrintObj(obj, w); err != nil { 218 return errors.E(op, err) 219 } 220 } 221 if err := w.Flush(); err != nil { 222 return errors.E(op, err) 223 } 224 225 return nil 226 } 227 228 func (r *runner) packageRevisionMatches(o *unstructured.Unstructured) (bool, error) { 229 packageName, _, err := unstructured.NestedString(o.Object, "spec", "packageName") 230 if err != nil { 231 return false, err 232 } 233 revision, _, err := unstructured.NestedString(o.Object, "spec", "revision") 234 if err != nil { 235 return false, err 236 } 237 if r.packageName != "" && r.packageName != packageName { 238 return false, nil 239 } 240 if r.revision != "" && r.revision != revision { 241 return false, nil 242 } 243 return true, nil 244 } 245 246 func findColumn(cols []metav1.TableColumnDefinition, name string) int { 247 for i := range cols { 248 if cols[i].Name == name { 249 return i 250 } 251 } 252 return -1 253 } 254 255 func getStringCell(cells []interface{}, col int) (string, bool) { 256 if col < 0 { 257 return "", false 258 } 259 s, ok := cells[col].(string) 260 return s, ok 261 } 262 263 func (r *runner) filterTableRows(table *metav1.Table) error { 264 filtered := make([]metav1.TableRow, 0, len(table.Rows)) 265 packageNameCol := findColumn(table.ColumnDefinitions, "Package") 266 revisionCol := findColumn(table.ColumnDefinitions, "Revision") 267 268 for i := range table.Rows { 269 row := &table.Rows[i] 270 271 if packageName, ok := getStringCell(row.Cells, packageNameCol); ok { 272 if r.packageName != "" && r.packageName != packageName { 273 continue 274 } 275 } 276 if revision, ok := getStringCell(row.Cells, revisionCol); ok { 277 if r.revision != "" && r.revision != revision { 278 continue 279 } 280 } 281 282 // Row matches 283 filtered = append(filtered, *row) 284 } 285 table.Rows = filtered 286 return nil 287 }