/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package exec import ( "reflect" "testing" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" clientauthenticationv1alpha1 "k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1" clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1" ) // TestV1beta1ClusterTypesAreSynced ensures that clientauthenticationv1beta1.Cluster stays in sync // with clientcmdv1.Cluster. // // We want clientauthenticationv1beta1.Cluster to offer the same knobs as clientcmdv1.Cluster to // allow someone to connect to the kubernetes API. This test should fail if a new field is added to // one of the structs without updating the other. func TestV1beta1ClusterTypesAreSynced(t *testing.T) { t.Parallel() execType := reflect.TypeOf(clientauthenticationv1beta1.Cluster{}) clientcmdType := reflect.TypeOf(clientcmdv1.Cluster{}) t.Run("exec cluster fields match clientcmd cluster fields", func(t *testing.T) { t.Parallel() // These are fields that are specific to Cluster and shouldn't be in clientcmdv1.Cluster. execSkippedFieldNames := sets.NewString( // Cluster uses Config to provide its cluster-specific configuration object. "Config", ) for i := 0; i < execType.NumField(); i++ { execField := execType.Field(i) if execSkippedFieldNames.Has(execField.Name) { continue } t.Run(execField.Name, func(t *testing.T) { t.Parallel() clientcmdField, ok := clientcmdType.FieldByName(execField.Name) if !ok { t.Errorf("unknown field (please add field to clientcmdv1.Cluster): '%s'", execField.Name) } else if execField.Type != clientcmdField.Type { t.Errorf( "type mismatch (please update Cluster.%s field type to match clientcmdv1.Cluster.%s field type): %q != %q", execField.Name, clientcmdField.Name, execField.Type, clientcmdField.Type, ) } else if execField.Tag != clientcmdField.Tag { t.Errorf( "tag mismatch (please update Cluster.%s tag to match clientcmdv1.Cluster.%s tag): %q != %q", execField.Name, clientcmdField.Name, execField.Tag, clientcmdField.Tag, ) } }) } }) t.Run("clientcmd cluster fields match exec cluster fields", func(t *testing.T) { t.Parallel() // These are the fields that we don't want to shadow from clientcmdv1.Cluster. clientcmdSkippedFieldNames := sets.NewString( // CA data will be passed via CertificateAuthorityData, so we don't need this field. "CertificateAuthority", // Cluster uses Config to provide its cluster-specific configuration object. "Extensions", ) for i := 0; i < clientcmdType.NumField(); i++ { clientcmdField := clientcmdType.Field(i) if clientcmdSkippedFieldNames.Has(clientcmdField.Name) { continue } t.Run(clientcmdField.Name, func(t *testing.T) { t.Parallel() execField, ok := execType.FieldByName(clientcmdField.Name) if !ok { t.Errorf("unknown field (please add field to Cluster): '%s'", clientcmdField.Name) } else if clientcmdField.Type != execField.Type { t.Errorf( "type mismatch (please update clientcmdv1.Cluster.%s field type to match Cluster.%s field type): %q != %q", clientcmdField.Name, execField.Name, clientcmdField.Type, execField.Type, ) } else if clientcmdField.Tag != execField.Tag { t.Errorf( "tag mismatch (please update clientcmdv1.Cluster.%s tag to match Cluster.%s tag): %q != %q", clientcmdField.Name, execField.Name, clientcmdField.Tag, execField.Tag, ) } }) } }) } // TestAllClusterTypesAreSynced is a TODO so that we remember to write a test similar to // TestV1beta1ClusterTypesAreSynced for any future ExecCredential version. It should start failing // when someone adds support for any other ExecCredential type to this package. func TestAllClusterTypesAreSynced(t *testing.T) { versionsThatDontNeedTests := sets.NewString( // The internal Cluster type should only be used...internally...and therefore doesn't // necessarily need to be synced with clientcmdv1. runtime.APIVersionInternal, // V1alpha1 does not contain a Cluster type. clientauthenticationv1alpha1.SchemeGroupVersion.Version, // We have a test for v1beta1 above. clientauthenticationv1beta1.SchemeGroupVersion.Version, ) for gvk := range scheme.AllKnownTypes() { if gvk.Group == clientauthenticationv1beta1.SchemeGroupVersion.Group && gvk.Kind == "ExecCredential" { if !versionsThatDontNeedTests.Has(gvk.Version) { t.Errorf( "TODO: add test similar to TestV1beta1ClusterTypesAreSynced for client.authentication.k8s.io/%s", gvk.Version, ) } } } }