From 7495ab52293423dcc6f4d83a86c21b4e440dce8e Mon Sep 17 00:00:00 2001 From: Maciej Szulik Date: Fri, 25 May 2018 17:33:22 +0200 Subject: [PATCH] Remove Generators from Factory --- pkg/kubectl/cmd/expose.go | 2 +- pkg/kubectl/cmd/run.go | 4 +- pkg/kubectl/cmd/util/BUILD | 1 + pkg/kubectl/cmd/util/factory.go | 3 - pkg/kubectl/cmd/util/factory_client_access.go | 222 ---------------- pkg/kubectl/cmd/util/generator.go | 238 ++++++++++++++++++ 6 files changed, 242 insertions(+), 228 deletions(-) create mode 100644 pkg/kubectl/cmd/util/generator.go diff --git a/pkg/kubectl/cmd/expose.go b/pkg/kubectl/cmd/expose.go index d36d47d736d..944af7f770e 100644 --- a/pkg/kubectl/cmd/expose.go +++ b/pkg/kubectl/cmd/expose.go @@ -188,7 +188,7 @@ func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) e return err } - o.Generators = f.Generators + o.Generators = cmdutil.GeneratorFn o.Builder = f.NewBuilder() o.CanBeExposed = polymorphichelpers.CanBeExposedFn o.ClientForMapping = f.ClientForMapping diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go index de450080765..a239b34e5a7 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -321,7 +321,7 @@ func (o *RunOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e } } - generators := f.Generators("run") + generators := cmdutil.GeneratorFn("run") generator, found := generators[generatorName] if !found { return cmdutil.UsageErrorf(cmd, "generator %q not found", generatorName) @@ -573,7 +573,7 @@ func verifyImagePullPolicy(cmd *cobra.Command) error { } func (o *RunOptions) generateService(f cmdutil.Factory, cmd *cobra.Command, serviceGenerator string, paramsIn map[string]interface{}, namespace string) (*RunObject, error) { - generators := f.Generators("expose") + generators := cmdutil.GeneratorFn("expose") generator, found := generators[serviceGenerator] if !found { return nil, fmt.Errorf("missing service generator: %s", serviceGenerator) diff --git a/pkg/kubectl/cmd/util/BUILD b/pkg/kubectl/cmd/util/BUILD index fa44ccc6917..026f03db50c 100644 --- a/pkg/kubectl/cmd/util/BUILD +++ b/pkg/kubectl/cmd/util/BUILD @@ -6,6 +6,7 @@ go_library( "conversion.go", "factory.go", "factory_client_access.go", + "generator.go", "helpers.go", "kubectl_match_version.go", "printing.go", diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index 0e7f0724797..c3303c6727c 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -28,7 +28,6 @@ import ( scaleclient "k8s.io/client-go/scale" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" @@ -68,8 +67,6 @@ type Factory interface { // other namespace is specified and whether the namespace was // overridden. DefaultNamespace() (string, bool, error) - // Generators returns the generators for the provided command - Generators(cmdName string) map[string]kubectl.Generator // Returns a RESTClient for working with the specified RESTMapping or an error. This is intended // for working with arbitrary resources and is not guaranteed to point to a Kubernetes APIServer. diff --git a/pkg/kubectl/cmd/util/factory_client_access.go b/pkg/kubectl/cmd/util/factory_client_access.go index c9cbda6bdde..8b806097680 100644 --- a/pkg/kubectl/cmd/util/factory_client_access.go +++ b/pkg/kubectl/cmd/util/factory_client_access.go @@ -19,22 +19,11 @@ limitations under the License. package util import ( - "fmt" - "io" "sync" - appsv1 "k8s.io/api/apps/v1" - appsv1beta1 "k8s.io/api/apps/v1beta1" - batchv1 "k8s.io/api/batch/v1" - batchv1beta1 "k8s.io/api/batch/v1beta1" - batchv2alpha1 "k8s.io/api/batch/v2alpha1" - extensionsv1beta1 "k8s.io/api/extensions/v1beta1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" @@ -44,7 +33,6 @@ import ( "k8s.io/kubernetes/pkg/api/legacyscheme" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" openapivalidation "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation" "k8s.io/kubernetes/pkg/kubectl/genericclioptions" @@ -223,216 +211,6 @@ func (f *factoryImpl) ScaleClient() (scaleclient.ScalesGetter, error) { return scaleclient.New(restClient, mapper, dynamic.LegacyAPIPathResolverFunc, resolver), nil } -const ( - // TODO(sig-cli): Enforce consistent naming for generators here. - // See discussion in https://github.com/kubernetes/kubernetes/issues/46237 - // before you add any more. - RunV1GeneratorName = "run/v1" - RunPodV1GeneratorName = "run-pod/v1" - ServiceV1GeneratorName = "service/v1" - ServiceV2GeneratorName = "service/v2" - ServiceNodePortGeneratorV1Name = "service-nodeport/v1" - ServiceClusterIPGeneratorV1Name = "service-clusterip/v1" - ServiceLoadBalancerGeneratorV1Name = "service-loadbalancer/v1" - ServiceExternalNameGeneratorV1Name = "service-externalname/v1" - ServiceAccountV1GeneratorName = "serviceaccount/v1" - HorizontalPodAutoscalerV1GeneratorName = "horizontalpodautoscaler/v1" - DeploymentV1Beta1GeneratorName = "deployment/v1beta1" - DeploymentAppsV1Beta1GeneratorName = "deployment/apps.v1beta1" - DeploymentBasicV1Beta1GeneratorName = "deployment-basic/v1beta1" - DeploymentBasicAppsV1Beta1GeneratorName = "deployment-basic/apps.v1beta1" - DeploymentBasicAppsV1GeneratorName = "deployment-basic/apps.v1" - JobV1GeneratorName = "job/v1" - CronJobV2Alpha1GeneratorName = "cronjob/v2alpha1" - CronJobV1Beta1GeneratorName = "cronjob/v1beta1" - NamespaceV1GeneratorName = "namespace/v1" - ResourceQuotaV1GeneratorName = "resourcequotas/v1" - SecretV1GeneratorName = "secret/v1" - SecretForDockerRegistryV1GeneratorName = "secret-for-docker-registry/v1" - SecretForTLSV1GeneratorName = "secret-for-tls/v1" - ConfigMapV1GeneratorName = "configmap/v1" - ClusterRoleBindingV1GeneratorName = "clusterrolebinding.rbac.authorization.k8s.io/v1alpha1" - RoleBindingV1GeneratorName = "rolebinding.rbac.authorization.k8s.io/v1alpha1" - ClusterV1Beta1GeneratorName = "cluster/v1beta1" - PodDisruptionBudgetV1GeneratorName = "poddisruptionbudget/v1beta1" - PodDisruptionBudgetV2GeneratorName = "poddisruptionbudget/v1beta1/v2" - PriorityClassV1Alpha1GeneratorName = "priorityclass/v1alpha1" -) - -// DefaultGenerators returns the set of default generators for use in Factory instances -func DefaultGenerators(cmdName string) map[string]kubectl.Generator { - var generator map[string]kubectl.Generator - switch cmdName { - case "expose": - generator = map[string]kubectl.Generator{ - ServiceV1GeneratorName: kubectl.ServiceGeneratorV1{}, - ServiceV2GeneratorName: kubectl.ServiceGeneratorV2{}, - } - case "service-clusterip": - generator = map[string]kubectl.Generator{ - ServiceClusterIPGeneratorV1Name: kubectl.ServiceClusterIPGeneratorV1{}, - } - case "service-nodeport": - generator = map[string]kubectl.Generator{ - ServiceNodePortGeneratorV1Name: kubectl.ServiceNodePortGeneratorV1{}, - } - case "service-loadbalancer": - generator = map[string]kubectl.Generator{ - ServiceLoadBalancerGeneratorV1Name: kubectl.ServiceLoadBalancerGeneratorV1{}, - } - case "deployment": - // Create Deployment has only StructuredGenerators and no - // param-based Generators. - // The StructuredGenerators are as follows (as of 2018-03-16): - // DeploymentBasicV1Beta1GeneratorName -> kubectl.DeploymentBasicGeneratorV1 - // DeploymentBasicAppsV1Beta1GeneratorName -> kubectl.DeploymentBasicAppsGeneratorV1Beta1 - // DeploymentBasicAppsV1GeneratorName -> kubectl.DeploymentBasicAppsGeneratorV1 - generator = map[string]kubectl.Generator{} - case "run": - generator = map[string]kubectl.Generator{ - RunV1GeneratorName: kubectl.BasicReplicationController{}, - RunPodV1GeneratorName: kubectl.BasicPod{}, - DeploymentV1Beta1GeneratorName: kubectl.DeploymentV1Beta1{}, - DeploymentAppsV1Beta1GeneratorName: kubectl.DeploymentAppsV1Beta1{}, - JobV1GeneratorName: kubectl.JobV1{}, - CronJobV2Alpha1GeneratorName: kubectl.CronJobV2Alpha1{}, - CronJobV1Beta1GeneratorName: kubectl.CronJobV1Beta1{}, - } - case "namespace": - generator = map[string]kubectl.Generator{ - NamespaceV1GeneratorName: kubectl.NamespaceGeneratorV1{}, - } - case "quota": - generator = map[string]kubectl.Generator{ - ResourceQuotaV1GeneratorName: kubectl.ResourceQuotaGeneratorV1{}, - } - case "secret": - generator = map[string]kubectl.Generator{ - SecretV1GeneratorName: kubectl.SecretGeneratorV1{}, - } - case "secret-for-docker-registry": - generator = map[string]kubectl.Generator{ - SecretForDockerRegistryV1GeneratorName: kubectl.SecretForDockerRegistryGeneratorV1{}, - } - case "secret-for-tls": - generator = map[string]kubectl.Generator{ - SecretForTLSV1GeneratorName: kubectl.SecretForTLSGeneratorV1{}, - } - } - - return generator -} - -// fallbackGeneratorNameIfNecessary returns the name of the old generator -// if server does not support new generator. Otherwise, the -// generator string is returned unchanged. -// -// If the generator name is changed, print a warning message to let the user -// know. -func FallbackGeneratorNameIfNecessary( - generatorName string, - discoveryClient discovery.DiscoveryInterface, - cmdErr io.Writer, -) (string, error) { - switch generatorName { - case DeploymentAppsV1Beta1GeneratorName: - hasResource, err := HasResource(discoveryClient, appsv1beta1.SchemeGroupVersion.WithResource("deployments")) - if err != nil { - return "", err - } - if !hasResource { - return FallbackGeneratorNameIfNecessary(DeploymentV1Beta1GeneratorName, discoveryClient, cmdErr) - } - case DeploymentV1Beta1GeneratorName: - hasResource, err := HasResource(discoveryClient, extensionsv1beta1.SchemeGroupVersion.WithResource("deployments")) - if err != nil { - return "", err - } - if !hasResource { - return RunV1GeneratorName, nil - } - case DeploymentBasicAppsV1GeneratorName: - hasResource, err := HasResource(discoveryClient, appsv1.SchemeGroupVersion.WithResource("deployments")) - if err != nil { - return "", err - } - if !hasResource { - return FallbackGeneratorNameIfNecessary(DeploymentBasicAppsV1Beta1GeneratorName, discoveryClient, cmdErr) - } - case DeploymentBasicAppsV1Beta1GeneratorName: - hasResource, err := HasResource(discoveryClient, appsv1beta1.SchemeGroupVersion.WithResource("deployments")) - if err != nil { - return "", err - } - if !hasResource { - return DeploymentBasicV1Beta1GeneratorName, nil - } - case JobV1GeneratorName: - hasResource, err := HasResource(discoveryClient, batchv1.SchemeGroupVersion.WithResource("jobs")) - if err != nil { - return "", err - } - if !hasResource { - return RunPodV1GeneratorName, nil - } - case CronJobV1Beta1GeneratorName: - hasResource, err := HasResource(discoveryClient, batchv1beta1.SchemeGroupVersion.WithResource("cronjobs")) - if err != nil { - return "", err - } - if !hasResource { - return FallbackGeneratorNameIfNecessary(CronJobV2Alpha1GeneratorName, discoveryClient, cmdErr) - } - case CronJobV2Alpha1GeneratorName: - hasResource, err := HasResource(discoveryClient, batchv2alpha1.SchemeGroupVersion.WithResource("cronjobs")) - if err != nil { - return "", err - } - if !hasResource { - return JobV1GeneratorName, nil - } - } - return generatorName, nil -} - -func Warning(cmdErr io.Writer, newGeneratorName, oldGeneratorName string) { - fmt.Fprintf(cmdErr, "WARNING: New generator %q specified, "+ - "but it isn't available. "+ - "Falling back to %q.\n", - newGeneratorName, - oldGeneratorName, - ) -} - -func HasResource(client discovery.DiscoveryInterface, resource schema.GroupVersionResource) (bool, error) { - resources, err := client.ServerResourcesForGroupVersion(resource.GroupVersion().String()) - if apierrors.IsNotFound(err) { - // entire group is missing - return false, nil - } - if err != nil { - // other errors error - return false, fmt.Errorf("failed to discover supported resources: %v", err) - } - for _, serverResource := range resources.APIResources { - if serverResource.Name == resource.Resource { - return true, nil - } - } - return false, nil -} - -func Contains(resourcesList []*metav1.APIResourceList, resource schema.GroupVersionResource) bool { - resources := discovery.FilteredBy(discovery.ResourcePredicateFunc(func(gv string, r *metav1.APIResource) bool { - return resource.GroupVersion().String() == gv && resource.Resource == r.Name - }), resourcesList) - return len(resources) != 0 -} - -func (f *factoryImpl) Generators(cmdName string) map[string]kubectl.Generator { - return DefaultGenerators(cmdName) -} - // this method exists to help us find the points still relying on internal types. func InternalVersionDecoder() runtime.Decoder { return legacyscheme.Codecs.UniversalDecoder() diff --git a/pkg/kubectl/cmd/util/generator.go b/pkg/kubectl/cmd/util/generator.go new file mode 100644 index 00000000000..551f9e94c10 --- /dev/null +++ b/pkg/kubectl/cmd/util/generator.go @@ -0,0 +1,238 @@ +/* +Copyright 2018 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 util + +import ( + "fmt" + "io" + + appsv1 "k8s.io/api/apps/v1" + appsv1beta1 "k8s.io/api/apps/v1beta1" + batchv1 "k8s.io/api/batch/v1" + batchv1beta1 "k8s.io/api/batch/v1beta1" + batchv2alpha1 "k8s.io/api/batch/v2alpha1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + "k8s.io/kubernetes/pkg/kubectl" +) + +const ( + // TODO(sig-cli): Enforce consistent naming for generators here. + // See discussion in https://github.com/kubernetes/kubernetes/issues/46237 + // before you add any more. + RunV1GeneratorName = "run/v1" + RunPodV1GeneratorName = "run-pod/v1" + ServiceV1GeneratorName = "service/v1" + ServiceV2GeneratorName = "service/v2" + ServiceNodePortGeneratorV1Name = "service-nodeport/v1" + ServiceClusterIPGeneratorV1Name = "service-clusterip/v1" + ServiceLoadBalancerGeneratorV1Name = "service-loadbalancer/v1" + ServiceExternalNameGeneratorV1Name = "service-externalname/v1" + ServiceAccountV1GeneratorName = "serviceaccount/v1" + HorizontalPodAutoscalerV1GeneratorName = "horizontalpodautoscaler/v1" + DeploymentV1Beta1GeneratorName = "deployment/v1beta1" + DeploymentAppsV1Beta1GeneratorName = "deployment/apps.v1beta1" + DeploymentBasicV1Beta1GeneratorName = "deployment-basic/v1beta1" + DeploymentBasicAppsV1Beta1GeneratorName = "deployment-basic/apps.v1beta1" + DeploymentBasicAppsV1GeneratorName = "deployment-basic/apps.v1" + JobV1GeneratorName = "job/v1" + CronJobV2Alpha1GeneratorName = "cronjob/v2alpha1" + CronJobV1Beta1GeneratorName = "cronjob/v1beta1" + NamespaceV1GeneratorName = "namespace/v1" + ResourceQuotaV1GeneratorName = "resourcequotas/v1" + SecretV1GeneratorName = "secret/v1" + SecretForDockerRegistryV1GeneratorName = "secret-for-docker-registry/v1" + SecretForTLSV1GeneratorName = "secret-for-tls/v1" + ConfigMapV1GeneratorName = "configmap/v1" + ClusterRoleBindingV1GeneratorName = "clusterrolebinding.rbac.authorization.k8s.io/v1alpha1" + RoleBindingV1GeneratorName = "rolebinding.rbac.authorization.k8s.io/v1alpha1" + ClusterV1Beta1GeneratorName = "cluster/v1beta1" + PodDisruptionBudgetV1GeneratorName = "poddisruptionbudget/v1beta1" + PodDisruptionBudgetV2GeneratorName = "poddisruptionbudget/v1beta1/v2" + PriorityClassV1Alpha1GeneratorName = "priorityclass/v1alpha1" +) + +// GeneratorFunc returns the generators for the provided command +type GeneratorFunc func(cmdName string) map[string]kubectl.Generator + +// GeneratorFn gives a way to easily override the function for unit testing if needed +var GeneratorFn GeneratorFunc = defaultGenerators + +// defaultGenerators returns the set of default generators for use in Factory instances +func defaultGenerators(cmdName string) map[string]kubectl.Generator { + var generator map[string]kubectl.Generator + switch cmdName { + case "expose": + generator = map[string]kubectl.Generator{ + ServiceV1GeneratorName: kubectl.ServiceGeneratorV1{}, + ServiceV2GeneratorName: kubectl.ServiceGeneratorV2{}, + } + case "service-clusterip": + generator = map[string]kubectl.Generator{ + ServiceClusterIPGeneratorV1Name: kubectl.ServiceClusterIPGeneratorV1{}, + } + case "service-nodeport": + generator = map[string]kubectl.Generator{ + ServiceNodePortGeneratorV1Name: kubectl.ServiceNodePortGeneratorV1{}, + } + case "service-loadbalancer": + generator = map[string]kubectl.Generator{ + ServiceLoadBalancerGeneratorV1Name: kubectl.ServiceLoadBalancerGeneratorV1{}, + } + case "deployment": + // Create Deployment has only StructuredGenerators and no + // param-based Generators. + // The StructuredGenerators are as follows (as of 2018-03-16): + // DeploymentBasicV1Beta1GeneratorName -> kubectl.DeploymentBasicGeneratorV1 + // DeploymentBasicAppsV1Beta1GeneratorName -> kubectl.DeploymentBasicAppsGeneratorV1Beta1 + // DeploymentBasicAppsV1GeneratorName -> kubectl.DeploymentBasicAppsGeneratorV1 + generator = map[string]kubectl.Generator{} + case "run": + generator = map[string]kubectl.Generator{ + RunV1GeneratorName: kubectl.BasicReplicationController{}, + RunPodV1GeneratorName: kubectl.BasicPod{}, + DeploymentV1Beta1GeneratorName: kubectl.DeploymentV1Beta1{}, + DeploymentAppsV1Beta1GeneratorName: kubectl.DeploymentAppsV1Beta1{}, + JobV1GeneratorName: kubectl.JobV1{}, + CronJobV2Alpha1GeneratorName: kubectl.CronJobV2Alpha1{}, + CronJobV1Beta1GeneratorName: kubectl.CronJobV1Beta1{}, + } + case "namespace": + generator = map[string]kubectl.Generator{ + NamespaceV1GeneratorName: kubectl.NamespaceGeneratorV1{}, + } + case "quota": + generator = map[string]kubectl.Generator{ + ResourceQuotaV1GeneratorName: kubectl.ResourceQuotaGeneratorV1{}, + } + case "secret": + generator = map[string]kubectl.Generator{ + SecretV1GeneratorName: kubectl.SecretGeneratorV1{}, + } + case "secret-for-docker-registry": + generator = map[string]kubectl.Generator{ + SecretForDockerRegistryV1GeneratorName: kubectl.SecretForDockerRegistryGeneratorV1{}, + } + case "secret-for-tls": + generator = map[string]kubectl.Generator{ + SecretForTLSV1GeneratorName: kubectl.SecretForTLSGeneratorV1{}, + } + } + + return generator +} + +// FallbackGeneratorNameIfNecessary returns the name of the old generator +// if server does not support new generator. Otherwise, the +// generator string is returned unchanged. +// +// If the generator name is changed, print a warning message to let the user +// know. +func FallbackGeneratorNameIfNecessary( + generatorName string, + discoveryClient discovery.DiscoveryInterface, + cmdErr io.Writer, +) (string, error) { + switch generatorName { + case DeploymentAppsV1Beta1GeneratorName: + hasResource, err := HasResource(discoveryClient, appsv1beta1.SchemeGroupVersion.WithResource("deployments")) + if err != nil { + return "", err + } + if !hasResource { + return FallbackGeneratorNameIfNecessary(DeploymentV1Beta1GeneratorName, discoveryClient, cmdErr) + } + case DeploymentV1Beta1GeneratorName: + hasResource, err := HasResource(discoveryClient, extensionsv1beta1.SchemeGroupVersion.WithResource("deployments")) + if err != nil { + return "", err + } + if !hasResource { + return RunV1GeneratorName, nil + } + case DeploymentBasicAppsV1GeneratorName: + hasResource, err := HasResource(discoveryClient, appsv1.SchemeGroupVersion.WithResource("deployments")) + if err != nil { + return "", err + } + if !hasResource { + return FallbackGeneratorNameIfNecessary(DeploymentBasicAppsV1Beta1GeneratorName, discoveryClient, cmdErr) + } + case DeploymentBasicAppsV1Beta1GeneratorName: + hasResource, err := HasResource(discoveryClient, appsv1beta1.SchemeGroupVersion.WithResource("deployments")) + if err != nil { + return "", err + } + if !hasResource { + return DeploymentBasicV1Beta1GeneratorName, nil + } + case JobV1GeneratorName: + hasResource, err := HasResource(discoveryClient, batchv1.SchemeGroupVersion.WithResource("jobs")) + if err != nil { + return "", err + } + if !hasResource { + return RunPodV1GeneratorName, nil + } + case CronJobV1Beta1GeneratorName: + hasResource, err := HasResource(discoveryClient, batchv1beta1.SchemeGroupVersion.WithResource("cronjobs")) + if err != nil { + return "", err + } + if !hasResource { + return FallbackGeneratorNameIfNecessary(CronJobV2Alpha1GeneratorName, discoveryClient, cmdErr) + } + case CronJobV2Alpha1GeneratorName: + hasResource, err := HasResource(discoveryClient, batchv2alpha1.SchemeGroupVersion.WithResource("cronjobs")) + if err != nil { + return "", err + } + if !hasResource { + return JobV1GeneratorName, nil + } + } + return generatorName, nil +} + +func HasResource(client discovery.DiscoveryInterface, resource schema.GroupVersionResource) (bool, error) { + resources, err := client.ServerResourcesForGroupVersion(resource.GroupVersion().String()) + if apierrors.IsNotFound(err) { + // entire group is missing + return false, nil + } + if err != nil { + // other errors error + return false, fmt.Errorf("failed to discover supported resources: %v", err) + } + for _, serverResource := range resources.APIResources { + if serverResource.Name == resource.Resource { + return true, nil + } + } + return false, nil +} + +func Warning(cmdErr io.Writer, newGeneratorName, oldGeneratorName string) { + fmt.Fprintf(cmdErr, "WARNING: New generator %q specified, "+ + "but it isn't available. "+ + "Falling back to %q.\n", + newGeneratorName, + oldGeneratorName, + ) +}