k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/apiserver/print_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes 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 apiserver 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "os" 24 "reflect" 25 "strings" 26 "testing" 27 "time" 28 29 "k8s.io/apimachinery/pkg/api/meta" 30 metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/runtime/schema" 33 "k8s.io/apimachinery/pkg/util/sets" 34 "k8s.io/cli-runtime/pkg/genericclioptions" 35 diskcached "k8s.io/client-go/discovery/cached/disk" 36 "k8s.io/client-go/tools/clientcmd" 37 clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 38 "k8s.io/kubectl/pkg/cmd/util" 39 "k8s.io/kubernetes/pkg/api/legacyscheme" 40 "k8s.io/kubernetes/pkg/printers" 41 printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" 42 "k8s.io/kubernetes/test/integration/framework" 43 ) 44 45 var kindAllowList = sets.NewString( 46 // k8s.io/api/core 47 "APIGroup", 48 "APIVersions", 49 "Binding", 50 "DeleteOptions", 51 "EphemeralContainers", 52 "ExportOptions", 53 "GetOptions", 54 "ListOptions", 55 "CreateOptions", 56 "UpdateOptions", 57 "PatchOptions", 58 "NodeProxyOptions", 59 "PodAttachOptions", 60 "PodExecOptions", 61 "PodPortForwardOptions", 62 "PodLogOptions", 63 "PodProxyOptions", 64 "PodStatusResult", 65 "RangeAllocation", 66 "ServiceProxyOptions", 67 "SerializedReference", 68 // -- 69 70 // k8s.io/api/admission 71 "AdmissionReview", 72 // -- 73 74 // k8s.io/api/authentication 75 "TokenRequest", 76 "TokenReview", 77 // -- 78 79 // k8s.io/api/authorization 80 "LocalSubjectAccessReview", 81 "SelfSubjectAccessReview", 82 "SelfSubjectRulesReview", 83 "SubjectAccessReview", 84 // -- 85 86 // k8s.io/api/autoscaling 87 "Scale", 88 // -- 89 90 // k8s.io/api/apps 91 "DeploymentRollback", 92 // -- 93 94 // k8s.io/api/batch 95 "JobTemplate", 96 // -- 97 98 // k8s.io/api/imagepolicy 99 "ImageReview", 100 // -- 101 102 // k8s.io/api/policy 103 "Eviction", 104 // -- 105 106 // k8s.io/apimachinery/pkg/apis/meta 107 "WatchEvent", 108 "Status", 109 // -- 110 ) 111 112 // TODO (soltysh): this list has to go down to 0! 113 var missingHanlders = sets.NewString( 114 "ClusterRole", 115 "LimitRange", 116 "ResourceQuota", 117 "Role", 118 "PriorityClass", 119 "AuditSink", 120 ) 121 122 func TestServerSidePrint(t *testing.T) { 123 _, clientSet, kubeConfig, tearDownFn := setupWithResources(t, 124 // additional groupversions needed for the test to run 125 []schema.GroupVersion{ 126 {Group: "discovery.k8s.io", Version: "v1"}, 127 {Group: "discovery.k8s.io", Version: "v1beta1"}, 128 {Group: "rbac.authorization.k8s.io", Version: "v1alpha1"}, 129 {Group: "scheduling.k8s.io", Version: "v1"}, 130 {Group: "storage.k8s.io", Version: "v1alpha1"}, 131 {Group: "storage.k8s.io", Version: "v1beta1"}, 132 {Group: "extensions", Version: "v1beta1"}, 133 {Group: "node.k8s.io", Version: "v1"}, 134 {Group: "node.k8s.io", Version: "v1alpha1"}, 135 {Group: "node.k8s.io", Version: "v1beta1"}, 136 {Group: "flowcontrol.apiserver.k8s.io", Version: "v1alpha1"}, 137 {Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta1"}, 138 {Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta2"}, 139 {Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta3"}, 140 {Group: "flowcontrol.apiserver.k8s.io", Version: "v1"}, 141 {Group: "internal.apiserver.k8s.io", Version: "v1alpha1"}, 142 }, 143 []schema.GroupVersionResource{}, 144 ) 145 defer tearDownFn() 146 147 ns := framework.CreateNamespaceOrDie(clientSet, "server-print", t) 148 defer framework.DeleteNamespaceOrDie(clientSet, ns, t) 149 150 tableParam := fmt.Sprintf("application/json;as=Table;g=%s;v=%s, application/json", metav1beta1.GroupName, metav1beta1.SchemeGroupVersion.Version) 151 printer := newFakePrinter(printersinternal.AddHandlers) 152 153 configFlags := genericclioptions.NewTestConfigFlags(). 154 WithClientConfig(clientcmd.NewDefaultClientConfig(*createKubeConfig(kubeConfig.Host), &clientcmd.ConfigOverrides{})) 155 156 restConfig, err := configFlags.ToRESTConfig() 157 if err != nil { 158 t.Errorf("unexpected error: %v", err) 159 } 160 161 cacheDir, err := os.MkdirTemp(os.TempDir(), "test-integration-apiserver-print") 162 if err != nil { 163 t.Errorf("unexpected error: %v", err) 164 } 165 defer func() { 166 os.Remove(cacheDir) 167 }() 168 169 cachedClient, err := diskcached.NewCachedDiscoveryClientForConfig(restConfig, cacheDir, "", time.Duration(10*time.Minute)) 170 if err != nil { 171 t.Errorf("unexpected error: %v", err) 172 } 173 174 configFlags.WithDiscoveryClient(cachedClient) 175 176 factory := util.NewFactory(configFlags) 177 mapper, err := factory.ToRESTMapper() 178 if err != nil { 179 t.Errorf("unexpected error getting mapper: %v", err) 180 return 181 } 182 for gvk, apiType := range legacyscheme.Scheme.AllKnownTypes() { 183 // we do not care about internal objects or lists // TODO make sure this is always true 184 if gvk.Version == runtime.APIVersionInternal || strings.HasSuffix(apiType.Name(), "List") { 185 continue 186 } 187 if kindAllowList.Has(gvk.Kind) || missingHanlders.Has(gvk.Kind) { 188 continue 189 } 190 191 t.Logf("Checking %s", gvk) 192 // read table definition as returned by the server 193 mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version) 194 if err != nil { 195 // if we have no mapping, we aren't serving it and we don't need to check its printer. 196 t.Logf("unexpected error getting mapping for GVK %s: %v", gvk, err) 197 continue 198 } 199 client, err := factory.ClientForMapping(mapping) 200 if err != nil { 201 t.Errorf("unexpected error getting client for GVK %s: %v", gvk, err) 202 continue 203 } 204 req := client.Get() 205 if mapping.Scope.Name() == meta.RESTScopeNameNamespace { 206 req = req.Namespace(ns.Name) 207 } 208 body, err := req.Resource(mapping.Resource.Resource).SetHeader("Accept", tableParam).Do(context.TODO()).Raw() 209 if err != nil { 210 t.Errorf("unexpected error getting %s: %v", gvk, err) 211 continue 212 } 213 actual, err := decodeIntoTable(body) 214 if err != nil { 215 t.Errorf("unexpected error decoding %s: %v", gvk, err) 216 continue 217 } 218 219 // get table definition used in printers 220 obj, err := legacyscheme.Scheme.New(gvk) 221 if err != nil { 222 t.Errorf("unexpected error creating %s: %v", gvk, err) 223 continue 224 } 225 intGV := gvk.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion() 226 intObj, err := legacyscheme.Scheme.ConvertToVersion(obj, intGV) 227 if err != nil { 228 t.Errorf("unexpected error converting %s to internal: %v", gvk, err) 229 continue 230 } 231 expectedColumnDefinitions, ok := printer.handlers[reflect.TypeOf(intObj)] 232 if !ok { 233 t.Errorf("missing handler for type %v", gvk) 234 continue 235 } 236 237 for _, e := range expectedColumnDefinitions { 238 for _, a := range actual.ColumnDefinitions { 239 if a.Name == e.Name && !reflect.DeepEqual(a, e) { 240 t.Errorf("unexpected difference in column definition %s for %s:\nexpected:\n%#v\nactual:\n%#v\n", e.Name, gvk, e, a) 241 } 242 } 243 } 244 } 245 } 246 247 type fakePrinter struct { 248 handlers map[reflect.Type][]metav1beta1.TableColumnDefinition 249 } 250 251 var _ printers.PrintHandler = &fakePrinter{} 252 253 func (f *fakePrinter) Handler(columns, columnsWithWide []string, printFunc interface{}) error { 254 return nil 255 } 256 257 func (f *fakePrinter) TableHandler(columns []metav1beta1.TableColumnDefinition, printFunc interface{}) error { 258 printFuncValue := reflect.ValueOf(printFunc) 259 objType := printFuncValue.Type().In(0) 260 f.handlers[objType] = columns 261 return nil 262 } 263 264 func (f *fakePrinter) DefaultTableHandler(columns []metav1beta1.TableColumnDefinition, printFunc interface{}) error { 265 return nil 266 } 267 268 func newFakePrinter(fns ...func(printers.PrintHandler)) *fakePrinter { 269 handlers := make(map[reflect.Type][]metav1beta1.TableColumnDefinition, len(fns)) 270 p := &fakePrinter{handlers: handlers} 271 for _, fn := range fns { 272 fn(p) 273 } 274 return p 275 } 276 277 func decodeIntoTable(body []byte) (*metav1beta1.Table, error) { 278 table := &metav1beta1.Table{} 279 err := json.Unmarshal(body, table) 280 if err != nil { 281 return nil, err 282 } 283 return table, nil 284 } 285 286 func createKubeConfig(url string) *clientcmdapi.Config { 287 clusterNick := "cluster" 288 userNick := "user" 289 contextNick := "context" 290 291 config := clientcmdapi.NewConfig() 292 293 cluster := clientcmdapi.NewCluster() 294 cluster.Server = url 295 cluster.InsecureSkipTLSVerify = true 296 config.Clusters[clusterNick] = cluster 297 298 context := clientcmdapi.NewContext() 299 context.Cluster = clusterNick 300 context.AuthInfo = userNick 301 config.Contexts[contextNick] = context 302 config.CurrentContext = contextNick 303 304 return config 305 }