mirror of https://github.com/k3s-io/k3s
Remove openapi/swagger
parent
5f15295c16
commit
d20c139f88
|
@ -110,7 +110,6 @@ kubernetes.tar.gz
|
||||||
# generated files in any directory
|
# generated files in any directory
|
||||||
# TODO(thockin): uncomment this when we stop committing the generated files.
|
# TODO(thockin): uncomment this when we stop committing the generated files.
|
||||||
#zz_generated.*
|
#zz_generated.*
|
||||||
zz_generated.openapi.go
|
|
||||||
zz_generated_*_test.go
|
zz_generated_*_test.go
|
||||||
|
|
||||||
# make-related metadata
|
# make-related metadata
|
||||||
|
|
|
@ -73,10 +73,6 @@ func createAggregatorConfig(
|
||||||
aggregatorscheme.Scheme,
|
aggregatorscheme.Scheme,
|
||||||
pluginInitializers...)
|
pluginInitializers...)
|
||||||
|
|
||||||
// the aggregator doesn't wire these up. It just delegates them to the kubeapiserver
|
|
||||||
genericConfig.EnableSwaggerUI = false
|
|
||||||
genericConfig.SwaggerConfig = nil
|
|
||||||
|
|
||||||
// copy the etcd options so we don't mutate originals.
|
// copy the etcd options so we don't mutate originals.
|
||||||
etcdOptions := *commandOptions.Etcd
|
etcdOptions := *commandOptions.Etcd
|
||||||
etcdOptions.StorageConfig.Paging = utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking)
|
etcdOptions.StorageConfig.Paging = utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking)
|
||||||
|
|
|
@ -31,10 +31,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
extensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||||
|
@ -43,7 +41,6 @@ import (
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
|
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
"k8s.io/apiserver/pkg/server/filters"
|
"k8s.io/apiserver/pkg/server/filters"
|
||||||
serveroptions "k8s.io/apiserver/pkg/server/options"
|
serveroptions "k8s.io/apiserver/pkg/server/options"
|
||||||
|
@ -58,13 +55,10 @@ import (
|
||||||
cloudprovider "k8s.io/cloud-provider"
|
cloudprovider "k8s.io/cloud-provider"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver"
|
aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver"
|
||||||
aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme"
|
|
||||||
openapi "k8s.io/kube-openapi/pkg/common"
|
|
||||||
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
|
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
"k8s.io/kubernetes/pkg/capabilities"
|
"k8s.io/kubernetes/pkg/capabilities"
|
||||||
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
|
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
|
||||||
generatedopenapi "k8s.io/kubernetes/pkg/generated/openapi"
|
|
||||||
"k8s.io/kubernetes/pkg/kubeapiserver"
|
"k8s.io/kubernetes/pkg/kubeapiserver"
|
||||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||||
kubeauthenticator "k8s.io/kubernetes/pkg/kubeapiserver/authenticator"
|
kubeauthenticator "k8s.io/kubernetes/pkg/kubeapiserver/authenticator"
|
||||||
|
@ -402,10 +396,6 @@ func buildGenericConfig(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(generatedopenapi.GetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(legacyscheme.Scheme, extensionsapiserver.Scheme, aggregatorscheme.Scheme))
|
|
||||||
genericConfig.OpenAPIConfig.PostProcessSpec = postProcessOpenAPISpecForBackwardCompatibility
|
|
||||||
genericConfig.OpenAPIConfig.Info.Title = "Kubernetes"
|
|
||||||
genericConfig.SwaggerConfig = genericapiserver.DefaultSwaggerConfig()
|
|
||||||
genericConfig.LongRunningFunc = filters.BasicLongRunningRequestCheck(
|
genericConfig.LongRunningFunc = filters.BasicLongRunningRequestCheck(
|
||||||
sets.NewString("watch", "proxy"),
|
sets.NewString("watch", "proxy"),
|
||||||
sets.NewString("attach", "exec", "proxy", "log", "portforward"),
|
sets.NewString("attach", "exec", "proxy", "log", "portforward"),
|
||||||
|
@ -443,7 +433,7 @@ func buildGenericConfig(
|
||||||
}
|
}
|
||||||
versionedInformers = clientgoinformers.NewSharedInformerFactory(clientgoExternalClient, 10*time.Minute)
|
versionedInformers = clientgoinformers.NewSharedInformerFactory(clientgoExternalClient, 10*time.Minute)
|
||||||
|
|
||||||
genericConfig.Authentication.Authenticator, genericConfig.OpenAPIConfig.SecurityDefinitions, err = BuildAuthenticator(s, clientgoExternalClient, versionedInformers)
|
genericConfig.Authentication.Authenticator, err = BuildAuthenticator(s, clientgoExternalClient, versionedInformers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = fmt.Errorf("invalid authentication config: %v", err)
|
lastErr = fmt.Errorf("invalid authentication config: %v", err)
|
||||||
return
|
return
|
||||||
|
@ -501,7 +491,7 @@ func buildGenericConfig(
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildAuthenticator constructs the authenticator
|
// BuildAuthenticator constructs the authenticator
|
||||||
func BuildAuthenticator(s *options.ServerRunOptions, extclient clientgoclientset.Interface, versionedInformer clientgoinformers.SharedInformerFactory) (authenticator.Request, *spec.SecurityDefinitions, error) {
|
func BuildAuthenticator(s *options.ServerRunOptions, extclient clientgoclientset.Interface, versionedInformer clientgoinformers.SharedInformerFactory) (authenticator.Request, error) {
|
||||||
authenticatorConfig := s.Authentication.ToAuthenticationConfig()
|
authenticatorConfig := s.Authentication.ToAuthenticationConfig()
|
||||||
if s.Authentication.ServiceAccounts.Lookup {
|
if s.Authentication.ServiceAccounts.Lookup {
|
||||||
authenticatorConfig.ServiceAccountTokenGetter = serviceaccountcontroller.NewGetterFromClient(extclient)
|
authenticatorConfig.ServiceAccountTokenGetter = serviceaccountcontroller.NewGetterFromClient(extclient)
|
||||||
|
@ -652,349 +642,3 @@ func readCAorNil(file string) ([]byte, error) {
|
||||||
}
|
}
|
||||||
return ioutil.ReadFile(file)
|
return ioutil.ReadFile(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostProcessSpec adds removed definitions for backward compatibility
|
|
||||||
func postProcessOpenAPISpecForBackwardCompatibility(s *spec.Swagger) (*spec.Swagger, error) {
|
|
||||||
compatibilityMap := map[string]string{
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1beta1.SelfSubjectAccessReview": "io.k8s.api.authorization.v1beta1.SelfSubjectAccessReview",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.GitRepoVolumeSource": "io.k8s.api.core.v1.GitRepoVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.ValidatingWebhookConfigurationList": "io.k8s.api.admissionregistration.v1alpha1.ValidatingWebhookConfigurationList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.EndpointPort": "io.k8s.api.core.v1.EndpointPort",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.SupplementalGroupsStrategyOptions": "io.k8s.api.extensions.v1beta1.SupplementalGroupsStrategyOptions",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PodStatus": "io.k8s.api.core.v1.PodStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1beta1.RoleBindingList": "io.k8s.api.rbac.v1beta1.RoleBindingList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.policy.v1beta1.PodDisruptionBudgetSpec": "io.k8s.api.policy.v1beta1.PodDisruptionBudgetSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.HTTPGetAction": "io.k8s.api.core.v1.HTTPGetAction",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1.ResourceAttributes": "io.k8s.api.authorization.v1.ResourceAttributes",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PersistentVolumeList": "io.k8s.api.core.v1.PersistentVolumeList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.batch.v2alpha1.CronJobSpec": "io.k8s.api.batch.v2alpha1.CronJobSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.CephFSVolumeSource": "io.k8s.api.core.v1.CephFSVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Affinity": "io.k8s.api.core.v1.Affinity",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1beta1.PolicyRule": "io.k8s.api.rbac.v1beta1.PolicyRule",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetSpec": "io.k8s.api.extensions.v1beta1.DaemonSetSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ProjectedVolumeSource": "io.k8s.api.core.v1.ProjectedVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.TCPSocketAction": "io.k8s.api.core.v1.TCPSocketAction",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSet": "io.k8s.api.extensions.v1beta1.DaemonSet",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressList": "io.k8s.api.extensions.v1beta1.IngressList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PodSpec": "io.k8s.api.core.v1.PodSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authentication.v1.TokenReview": "io.k8s.api.authentication.v1.TokenReview",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1beta1.SubjectAccessReview": "io.k8s.api.authorization.v1beta1.SubjectAccessReview",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.ClusterRoleBinding": "io.k8s.api.rbac.v1alpha1.ClusterRoleBinding",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Node": "io.k8s.api.core.v1.Node",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.ServiceReference": "io.k8s.api.admissionregistration.v1alpha1.ServiceReference",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentStatus": "io.k8s.api.extensions.v1beta1.DeploymentStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1beta1.RoleRef": "io.k8s.api.rbac.v1beta1.RoleRef",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.Scale": "io.k8s.api.apps.v1beta1.Scale",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.InitializerConfiguration": "io.k8s.api.admissionregistration.v1alpha1.InitializerConfiguration",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PhotonPersistentDiskVolumeSource": "io.k8s.api.core.v1.PhotonPersistentDiskVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PreferredSchedulingTerm": "io.k8s.api.core.v1.PreferredSchedulingTerm",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.batch.v1.JobSpec": "io.k8s.api.batch.v1.JobSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.EventSource": "io.k8s.api.core.v1.EventSource",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Container": "io.k8s.api.core.v1.Container",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.AdmissionHookClientConfig": "io.k8s.api.admissionregistration.v1alpha1.AdmissionHookClientConfig",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ResourceQuota": "io.k8s.api.core.v1.ResourceQuota",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.SecretList": "io.k8s.api.core.v1.SecretList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.NodeSystemInfo": "io.k8s.api.core.v1.NodeSystemInfo",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.PolicyRule": "io.k8s.api.rbac.v1alpha1.PolicyRule",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetSpec": "io.k8s.api.extensions.v1beta1.ReplicaSetSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.NodeStatus": "io.k8s.api.core.v1.NodeStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ResourceQuotaList": "io.k8s.api.core.v1.ResourceQuotaList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.HostPathVolumeSource": "io.k8s.api.core.v1.HostPathVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.certificates.v1beta1.CertificateSigningRequest": "io.k8s.api.certificates.v1beta1.CertificateSigningRequest",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressRule": "io.k8s.api.extensions.v1beta1.IngressRule",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicyPeer": "io.k8s.api.extensions.v1beta1.NetworkPolicyPeer",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.storage.v1.StorageClass": "io.k8s.api.storage.v1.StorageClass",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicyPeer": "io.k8s.api.networking.v1.NetworkPolicyPeer",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicyIngressRule": "io.k8s.api.networking.v1.NetworkPolicyIngressRule",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.StorageOSPersistentVolumeSource": "io.k8s.api.core.v1.StorageOSPersistentVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicyIngressRule": "io.k8s.api.extensions.v1beta1.NetworkPolicyIngressRule",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PodAffinity": "io.k8s.api.core.v1.PodAffinity",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.RollbackConfig": "io.k8s.api.extensions.v1beta1.RollbackConfig",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PodList": "io.k8s.api.core.v1.PodList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ScaleStatus": "io.k8s.api.extensions.v1beta1.ScaleStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ComponentCondition": "io.k8s.api.core.v1.ComponentCondition",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.certificates.v1beta1.CertificateSigningRequestList": "io.k8s.api.certificates.v1beta1.CertificateSigningRequestList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.ClusterRoleBindingList": "io.k8s.api.rbac.v1alpha1.ClusterRoleBindingList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.HorizontalPodAutoscalerCondition": "io.k8s.api.autoscaling.v2alpha1.HorizontalPodAutoscalerCondition",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ServiceList": "io.k8s.api.core.v1.ServiceList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.PodSecurityPolicy": "io.k8s.api.extensions.v1beta1.PodSecurityPolicy",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.batch.v1.JobCondition": "io.k8s.api.batch.v1.JobCondition",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentStatus": "io.k8s.api.apps.v1beta1.DeploymentStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Volume": "io.k8s.api.core.v1.Volume",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.RoleBindingList": "io.k8s.api.rbac.v1alpha1.RoleBindingList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.Rule": "io.k8s.api.admissionregistration.v1alpha1.Rule",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.InitializerConfigurationList": "io.k8s.api.admissionregistration.v1alpha1.InitializerConfigurationList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicy": "io.k8s.api.extensions.v1beta1.NetworkPolicy",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.ClusterRoleList": "io.k8s.api.rbac.v1alpha1.ClusterRoleList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ObjectFieldSelector": "io.k8s.api.core.v1.ObjectFieldSelector",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.EventList": "io.k8s.api.core.v1.EventList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.MetricStatus": "io.k8s.api.autoscaling.v2alpha1.MetricStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicyPort": "io.k8s.api.networking.v1.NetworkPolicyPort",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1beta1.RoleList": "io.k8s.api.rbac.v1beta1.RoleList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.RoleList": "io.k8s.api.rbac.v1alpha1.RoleList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentStrategy": "io.k8s.api.apps.v1beta1.DeploymentStrategy",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v1.CrossVersionObjectReference": "io.k8s.api.autoscaling.v1.CrossVersionObjectReference",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ConfigMapProjection": "io.k8s.api.core.v1.ConfigMapProjection",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.CrossVersionObjectReference": "io.k8s.api.autoscaling.v2alpha1.CrossVersionObjectReference",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.LoadBalancerStatus": "io.k8s.api.core.v1.LoadBalancerStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ISCSIVolumeSource": "io.k8s.api.core.v1.ISCSIVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.ControllerRevisionList": "io.k8s.api.apps.v1beta1.ControllerRevisionList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.EndpointSubset": "io.k8s.api.core.v1.EndpointSubset",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.SELinuxOptions": "io.k8s.api.core.v1.SELinuxOptions",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaimVolumeSource": "io.k8s.api.core.v1.PersistentVolumeClaimVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.MetricSpec": "io.k8s.api.autoscaling.v2alpha1.MetricSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.StatefulSetList": "io.k8s.api.apps.v1beta1.StatefulSetList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1beta1.ResourceAttributes": "io.k8s.api.authorization.v1beta1.ResourceAttributes",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Capabilities": "io.k8s.api.core.v1.Capabilities",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.Deployment": "io.k8s.api.extensions.v1beta1.Deployment",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Binding": "io.k8s.api.core.v1.Binding",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ReplicationControllerList": "io.k8s.api.core.v1.ReplicationControllerList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1.SelfSubjectAccessReview": "io.k8s.api.authorization.v1.SelfSubjectAccessReview",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authentication.v1beta1.UserInfo": "io.k8s.api.authentication.v1beta1.UserInfo",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.HostAlias": "io.k8s.api.core.v1.HostAlias",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.StatefulSetUpdateStrategy": "io.k8s.api.apps.v1beta1.StatefulSetUpdateStrategy",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressSpec": "io.k8s.api.extensions.v1beta1.IngressSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentCondition": "io.k8s.api.extensions.v1beta1.DeploymentCondition",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.GCEPersistentDiskVolumeSource": "io.k8s.api.core.v1.GCEPersistentDiskVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.Webhook": "io.k8s.api.admissionregistration.v1alpha1.Webhook",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.Scale": "io.k8s.api.extensions.v1beta1.Scale",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.HorizontalPodAutoscalerStatus": "io.k8s.api.autoscaling.v2alpha1.HorizontalPodAutoscalerStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.FlexVolumeSource": "io.k8s.api.core.v1.FlexVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.RollingUpdateDeployment": "io.k8s.api.extensions.v1beta1.RollingUpdateDeployment",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.ObjectMetricStatus": "io.k8s.api.autoscaling.v2alpha1.ObjectMetricStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Event": "io.k8s.api.core.v1.Event",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ResourceQuotaSpec": "io.k8s.api.core.v1.ResourceQuotaSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Handler": "io.k8s.api.core.v1.Handler",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressBackend": "io.k8s.api.extensions.v1beta1.IngressBackend",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.Role": "io.k8s.api.rbac.v1alpha1.Role",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.ObjectMetricSource": "io.k8s.api.autoscaling.v2alpha1.ObjectMetricSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.ResourceMetricStatus": "io.k8s.api.autoscaling.v2alpha1.ResourceMetricStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscalerSpec": "io.k8s.api.autoscaling.v1.HorizontalPodAutoscalerSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Lifecycle": "io.k8s.api.core.v1.Lifecycle",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.certificates.v1beta1.CertificateSigningRequestStatus": "io.k8s.api.certificates.v1beta1.CertificateSigningRequestStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ContainerStateRunning": "io.k8s.api.core.v1.ContainerStateRunning",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ServiceAccountList": "io.k8s.api.core.v1.ServiceAccountList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.HostPortRange": "io.k8s.api.extensions.v1beta1.HostPortRange",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.ControllerRevision": "io.k8s.api.apps.v1beta1.ControllerRevision",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ReplicationControllerSpec": "io.k8s.api.core.v1.ReplicationControllerSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ContainerStateTerminated": "io.k8s.api.core.v1.ContainerStateTerminated",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ReplicationControllerStatus": "io.k8s.api.core.v1.ReplicationControllerStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetList": "io.k8s.api.extensions.v1beta1.DaemonSetList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1.SelfSubjectAccessReviewSpec": "io.k8s.api.authorization.v1.SelfSubjectAccessReviewSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ComponentStatusList": "io.k8s.api.core.v1.ComponentStatusList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ContainerStateWaiting": "io.k8s.api.core.v1.ContainerStateWaiting",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.VolumeMount": "io.k8s.api.core.v1.VolumeMount",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Secret": "io.k8s.api.core.v1.Secret",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1beta1.ClusterRoleList": "io.k8s.api.rbac.v1beta1.ClusterRoleList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ConfigMapList": "io.k8s.api.core.v1.ConfigMapList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.storage.v1beta1.StorageClassList": "io.k8s.api.storage.v1beta1.StorageClassList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.HTTPIngressPath": "io.k8s.api.extensions.v1beta1.HTTPIngressPath",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.ClusterRole": "io.k8s.api.rbac.v1alpha1.ClusterRole",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.ResourceMetricSource": "io.k8s.api.autoscaling.v2alpha1.ResourceMetricSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentRollback": "io.k8s.api.extensions.v1beta1.DeploymentRollback",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaimSpec": "io.k8s.api.core.v1.PersistentVolumeClaimSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ReplicationController": "io.k8s.api.core.v1.ReplicationController",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.StatefulSetSpec": "io.k8s.api.apps.v1beta1.StatefulSetSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.SecurityContext": "io.k8s.api.core.v1.SecurityContext",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicySpec": "io.k8s.api.networking.v1.NetworkPolicySpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.LocalObjectReference": "io.k8s.api.core.v1.LocalObjectReference",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.RBDVolumeSource": "io.k8s.api.core.v1.RBDVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicySpec": "io.k8s.api.extensions.v1beta1.NetworkPolicySpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.KeyToPath": "io.k8s.api.core.v1.KeyToPath",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.WeightedPodAffinityTerm": "io.k8s.api.core.v1.WeightedPodAffinityTerm",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.PodsMetricStatus": "io.k8s.api.autoscaling.v2alpha1.PodsMetricStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.NodeAddress": "io.k8s.api.core.v1.NodeAddress",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.Ingress": "io.k8s.api.extensions.v1beta1.Ingress",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.policy.v1beta1.PodDisruptionBudget": "io.k8s.api.policy.v1beta1.PodDisruptionBudget",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ServicePort": "io.k8s.api.core.v1.ServicePort",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IDRange": "io.k8s.api.extensions.v1beta1.IDRange",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.SecretEnvSource": "io.k8s.api.core.v1.SecretEnvSource",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.NodeSelector": "io.k8s.api.core.v1.NodeSelector",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaimStatus": "io.k8s.api.core.v1.PersistentVolumeClaimStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentSpec": "io.k8s.api.apps.v1beta1.DeploymentSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1.NonResourceAttributes": "io.k8s.api.authorization.v1.NonResourceAttributes",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v1.ScaleStatus": "io.k8s.api.autoscaling.v1.ScaleStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PodCondition": "io.k8s.api.core.v1.PodCondition",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PodTemplateSpec": "io.k8s.api.core.v1.PodTemplateSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.StatefulSet": "io.k8s.api.apps.v1beta1.StatefulSet",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicyPort": "io.k8s.api.extensions.v1beta1.NetworkPolicyPort",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authentication.v1beta1.TokenReview": "io.k8s.api.authentication.v1beta1.TokenReview",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.LimitRangeSpec": "io.k8s.api.core.v1.LimitRangeSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.FlockerVolumeSource": "io.k8s.api.core.v1.FlockerVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.policy.v1beta1.Eviction": "io.k8s.api.policy.v1beta1.Eviction",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaimList": "io.k8s.api.core.v1.PersistentVolumeClaimList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.certificates.v1beta1.CertificateSigningRequestCondition": "io.k8s.api.certificates.v1beta1.CertificateSigningRequestCondition",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.DownwardAPIVolumeFile": "io.k8s.api.core.v1.DownwardAPIVolumeFile",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1beta1.LocalSubjectAccessReview": "io.k8s.api.authorization.v1beta1.LocalSubjectAccessReview",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.ScaleStatus": "io.k8s.api.apps.v1beta1.ScaleStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.HTTPIngressRuleValue": "io.k8s.api.extensions.v1beta1.HTTPIngressRuleValue",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.batch.v1.Job": "io.k8s.api.batch.v1.Job",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.ValidatingWebhookConfiguration": "io.k8s.api.admissionregistration.v1alpha1.ValidatingWebhookConfiguration",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1beta1.RoleBinding": "io.k8s.api.rbac.v1beta1.RoleBinding",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.FCVolumeSource": "io.k8s.api.core.v1.FCVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.EndpointAddress": "io.k8s.api.core.v1.EndpointAddress",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ContainerPort": "io.k8s.api.core.v1.ContainerPort",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1beta1.ClusterRoleBinding": "io.k8s.api.rbac.v1beta1.ClusterRoleBinding",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.GlusterfsVolumeSource": "io.k8s.api.core.v1.GlusterfsVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ResourceRequirements": "io.k8s.api.core.v1.ResourceRequirements",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.RollingUpdateDeployment": "io.k8s.api.apps.v1beta1.RollingUpdateDeployment",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.NamespaceStatus": "io.k8s.api.core.v1.NamespaceStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.RunAsUserStrategyOptions": "io.k8s.api.extensions.v1beta1.RunAsUserStrategyOptions",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Namespace": "io.k8s.api.core.v1.Namespace",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1.SubjectAccessReviewSpec": "io.k8s.api.authorization.v1.SubjectAccessReviewSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.HorizontalPodAutoscaler": "io.k8s.api.autoscaling.v2alpha1.HorizontalPodAutoscaler",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetCondition": "io.k8s.api.extensions.v1beta1.ReplicaSetCondition",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscalerStatus": "io.k8s.api.autoscaling.v1.HorizontalPodAutoscalerStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authentication.v1.TokenReviewStatus": "io.k8s.api.authentication.v1.TokenReviewStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PersistentVolume": "io.k8s.api.core.v1.PersistentVolume",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.FSGroupStrategyOptions": "io.k8s.api.extensions.v1beta1.FSGroupStrategyOptions",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PodSecurityContext": "io.k8s.api.core.v1.PodSecurityContext",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PodTemplate": "io.k8s.api.core.v1.PodTemplate",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1.LocalSubjectAccessReview": "io.k8s.api.authorization.v1.LocalSubjectAccessReview",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.StorageOSVolumeSource": "io.k8s.api.core.v1.StorageOSVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.NodeSelectorTerm": "io.k8s.api.core.v1.NodeSelectorTerm",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1beta1.Role": "io.k8s.api.rbac.v1beta1.Role",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ContainerStatus": "io.k8s.api.core.v1.ContainerStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1.SubjectAccessReviewStatus": "io.k8s.api.authorization.v1.SubjectAccessReviewStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authentication.v1.TokenReviewSpec": "io.k8s.api.authentication.v1.TokenReviewSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ConfigMap": "io.k8s.api.core.v1.ConfigMap",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ServiceStatus": "io.k8s.api.core.v1.ServiceStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1beta1.SelfSubjectAccessReviewSpec": "io.k8s.api.authorization.v1beta1.SelfSubjectAccessReviewSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.CinderVolumeSource": "io.k8s.api.core.v1.CinderVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.settings.v1alpha1.PodPresetSpec": "io.k8s.api.settings.v1alpha1.PodPresetSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1beta1.NonResourceAttributes": "io.k8s.api.authorization.v1beta1.NonResourceAttributes",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ContainerImage": "io.k8s.api.core.v1.ContainerImage",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ReplicationControllerCondition": "io.k8s.api.core.v1.ReplicationControllerCondition",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.EmptyDirVolumeSource": "io.k8s.api.core.v1.EmptyDirVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscalerList": "io.k8s.api.autoscaling.v1.HorizontalPodAutoscalerList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.batch.v1.JobList": "io.k8s.api.batch.v1.JobList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.NFSVolumeSource": "io.k8s.api.core.v1.NFSVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Pod": "io.k8s.api.core.v1.Pod",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ObjectReference": "io.k8s.api.core.v1.ObjectReference",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.Deployment": "io.k8s.api.apps.v1beta1.Deployment",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.storage.v1.StorageClassList": "io.k8s.api.storage.v1.StorageClassList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.AttachedVolume": "io.k8s.api.core.v1.AttachedVolume",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.AWSElasticBlockStoreVolumeSource": "io.k8s.api.core.v1.AWSElasticBlockStoreVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.batch.v2alpha1.CronJobList": "io.k8s.api.batch.v2alpha1.CronJobList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentSpec": "io.k8s.api.extensions.v1beta1.DeploymentSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.PodSecurityPolicyList": "io.k8s.api.extensions.v1beta1.PodSecurityPolicyList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PodAffinityTerm": "io.k8s.api.core.v1.PodAffinityTerm",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.HTTPHeader": "io.k8s.api.core.v1.HTTPHeader",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ConfigMapKeySelector": "io.k8s.api.core.v1.ConfigMapKeySelector",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.SecretKeySelector": "io.k8s.api.core.v1.SecretKeySelector",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentList": "io.k8s.api.extensions.v1beta1.DeploymentList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authentication.v1.UserInfo": "io.k8s.api.authentication.v1.UserInfo",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.LoadBalancerIngress": "io.k8s.api.core.v1.LoadBalancerIngress",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.DaemonEndpoint": "io.k8s.api.core.v1.DaemonEndpoint",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.NodeSelectorRequirement": "io.k8s.api.core.v1.NodeSelectorRequirement",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.batch.v2alpha1.CronJobStatus": "io.k8s.api.batch.v2alpha1.CronJobStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v1.Scale": "io.k8s.api.autoscaling.v1.Scale",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ScaleIOVolumeSource": "io.k8s.api.core.v1.ScaleIOVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PodAntiAffinity": "io.k8s.api.core.v1.PodAntiAffinity",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.PodSecurityPolicySpec": "io.k8s.api.extensions.v1beta1.PodSecurityPolicySpec",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.settings.v1alpha1.PodPresetList": "io.k8s.api.settings.v1alpha1.PodPresetList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.NodeAffinity": "io.k8s.api.core.v1.NodeAffinity",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentCondition": "io.k8s.api.apps.v1beta1.DeploymentCondition",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.NodeSpec": "io.k8s.api.core.v1.NodeSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.StatefulSetStatus": "io.k8s.api.apps.v1beta1.StatefulSetStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.RuleWithOperations": "io.k8s.api.admissionregistration.v1alpha1.RuleWithOperations",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressStatus": "io.k8s.api.extensions.v1beta1.IngressStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.LimitRangeList": "io.k8s.api.core.v1.LimitRangeList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.AzureDiskVolumeSource": "io.k8s.api.core.v1.AzureDiskVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetStatus": "io.k8s.api.extensions.v1beta1.ReplicaSetStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ComponentStatus": "io.k8s.api.core.v1.ComponentStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscaler": "io.k8s.api.autoscaling.v1.HorizontalPodAutoscaler",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicy": "io.k8s.api.networking.v1.NetworkPolicy",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.RollbackConfig": "io.k8s.api.apps.v1beta1.RollbackConfig",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.NodeCondition": "io.k8s.api.core.v1.NodeCondition",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.DownwardAPIProjection": "io.k8s.api.core.v1.DownwardAPIProjection",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.SELinuxStrategyOptions": "io.k8s.api.extensions.v1beta1.SELinuxStrategyOptions",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.NamespaceSpec": "io.k8s.api.core.v1.NamespaceSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.certificates.v1beta1.CertificateSigningRequestSpec": "io.k8s.api.certificates.v1beta1.CertificateSigningRequestSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ServiceSpec": "io.k8s.api.core.v1.ServiceSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1.SubjectAccessReview": "io.k8s.api.authorization.v1.SubjectAccessReview",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentList": "io.k8s.api.apps.v1beta1.DeploymentList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Toleration": "io.k8s.api.core.v1.Toleration",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicyList": "io.k8s.api.extensions.v1beta1.NetworkPolicyList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.PodsMetricSource": "io.k8s.api.autoscaling.v2alpha1.PodsMetricSource",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.EnvFromSource": "io.k8s.api.core.v1.EnvFromSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v1.ScaleSpec": "io.k8s.api.autoscaling.v1.ScaleSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PodTemplateList": "io.k8s.api.core.v1.PodTemplateList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.HorizontalPodAutoscalerSpec": "io.k8s.api.autoscaling.v2alpha1.HorizontalPodAutoscalerSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.SecretProjection": "io.k8s.api.core.v1.SecretProjection",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ResourceFieldSelector": "io.k8s.api.core.v1.ResourceFieldSelector",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PersistentVolumeSpec": "io.k8s.api.core.v1.PersistentVolumeSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ConfigMapVolumeSource": "io.k8s.api.core.v1.ConfigMapVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.HorizontalPodAutoscalerList": "io.k8s.api.autoscaling.v2alpha1.HorizontalPodAutoscalerList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authentication.v1beta1.TokenReviewStatus": "io.k8s.api.authentication.v1beta1.TokenReviewStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicyList": "io.k8s.api.networking.v1.NetworkPolicyList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Endpoints": "io.k8s.api.core.v1.Endpoints",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.LimitRangeItem": "io.k8s.api.core.v1.LimitRangeItem",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ServiceAccount": "io.k8s.api.core.v1.ServiceAccount",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ScaleSpec": "io.k8s.api.extensions.v1beta1.ScaleSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressTLS": "io.k8s.api.extensions.v1beta1.IngressTLS",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.batch.v2alpha1.CronJob": "io.k8s.api.batch.v2alpha1.CronJob",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.Subject": "io.k8s.api.rbac.v1alpha1.Subject",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetStatus": "io.k8s.api.extensions.v1beta1.DaemonSetStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.policy.v1beta1.PodDisruptionBudgetList": "io.k8s.api.policy.v1beta1.PodDisruptionBudgetList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.VsphereVirtualDiskVolumeSource": "io.k8s.api.core.v1.VsphereVirtualDiskVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.RoleRef": "io.k8s.api.rbac.v1alpha1.RoleRef",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PortworxVolumeSource": "io.k8s.api.core.v1.PortworxVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetList": "io.k8s.api.extensions.v1beta1.ReplicaSetList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.VolumeProjection": "io.k8s.api.core.v1.VolumeProjection",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.storage.v1beta1.StorageClass": "io.k8s.api.storage.v1beta1.StorageClass",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSet": "io.k8s.api.extensions.v1beta1.ReplicaSet",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentRollback": "io.k8s.api.apps.v1beta1.DeploymentRollback",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.RoleBinding": "io.k8s.api.rbac.v1alpha1.RoleBinding",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.AzureFileVolumeSource": "io.k8s.api.core.v1.AzureFileVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.policy.v1beta1.PodDisruptionBudgetStatus": "io.k8s.api.policy.v1beta1.PodDisruptionBudgetStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authentication.v1beta1.TokenReviewSpec": "io.k8s.api.authentication.v1beta1.TokenReviewSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.EndpointsList": "io.k8s.api.core.v1.EndpointsList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ConfigMapEnvSource": "io.k8s.api.core.v1.ConfigMapEnvSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.batch.v2alpha1.JobTemplateSpec": "io.k8s.api.batch.v2alpha1.JobTemplateSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetUpdateStrategy": "io.k8s.api.extensions.v1beta1.DaemonSetUpdateStrategy",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1beta1.SubjectAccessReviewSpec": "io.k8s.api.authorization.v1beta1.SubjectAccessReviewSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.LocalVolumeSource": "io.k8s.api.core.v1.LocalVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ContainerState": "io.k8s.api.core.v1.ContainerState",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Service": "io.k8s.api.core.v1.Service",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ExecAction": "io.k8s.api.core.v1.ExecAction",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Taint": "io.k8s.api.core.v1.Taint",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1beta1.Subject": "io.k8s.api.rbac.v1beta1.Subject",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.authorization.v1beta1.SubjectAccessReviewStatus": "io.k8s.api.authorization.v1beta1.SubjectAccessReviewStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1beta1.ClusterRoleBindingList": "io.k8s.api.rbac.v1beta1.ClusterRoleBindingList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.DownwardAPIVolumeSource": "io.k8s.api.core.v1.DownwardAPIVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.batch.v1.JobStatus": "io.k8s.api.batch.v1.JobStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.ResourceQuotaStatus": "io.k8s.api.core.v1.ResourceQuotaStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PersistentVolumeStatus": "io.k8s.api.core.v1.PersistentVolumeStatus",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaim": "io.k8s.api.core.v1.PersistentVolumeClaim",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.NodeDaemonEndpoints": "io.k8s.api.core.v1.NodeDaemonEndpoints",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.EnvVar": "io.k8s.api.core.v1.EnvVar",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.SecretVolumeSource": "io.k8s.api.core.v1.SecretVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.EnvVarSource": "io.k8s.api.core.v1.EnvVarSource",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.RollingUpdateStatefulSetStrategy": "io.k8s.api.apps.v1beta1.RollingUpdateStatefulSetStrategy",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.rbac.v1beta1.ClusterRole": "io.k8s.api.rbac.v1beta1.ClusterRole",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.Initializer": "io.k8s.api.admissionregistration.v1alpha1.Initializer",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentStrategy": "io.k8s.api.extensions.v1beta1.DeploymentStrategy",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.apps.v1beta1.ScaleSpec": "io.k8s.api.apps.v1beta1.ScaleSpec",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.settings.v1alpha1.PodPreset": "io.k8s.api.settings.v1alpha1.PodPreset",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.Probe": "io.k8s.api.core.v1.Probe",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.NamespaceList": "io.k8s.api.core.v1.NamespaceList",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.QuobyteVolumeSource": "io.k8s.api.core.v1.QuobyteVolumeSource",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.NodeList": "io.k8s.api.core.v1.NodeList",
|
|
||||||
"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.RollingUpdateDaemonSet": "io.k8s.api.extensions.v1beta1.RollingUpdateDaemonSet",
|
|
||||||
"io.k8s.kubernetes.pkg.api.v1.LimitRange": "io.k8s.api.core.v1.LimitRange",
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range compatibilityMap {
|
|
||||||
if _, found := s.Definitions[v]; !found {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s.Definitions[k] = spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Ref: spec.MustCreateRef("#/definitions/" + openapi.EscapeJsonPointer(v)),
|
|
||||||
Description: fmt.Sprintf("Deprecated. Please use %s instead.", v),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -341,7 +341,7 @@ func (s *KubeControllerManagerOptions) ApplyTo(c *kubecontrollerconfig.Config) e
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if s.SecureServing.BindPort != 0 || s.SecureServing.Listener != nil {
|
if s.SecureServing.BindPort != 0 || s.SecureServing.Listener != nil {
|
||||||
if err := s.Authentication.ApplyTo(&c.Authentication, c.SecureServing, nil); err != nil {
|
if err := s.Authentication.ApplyTo(&c.Authentication, c.SecureServing); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := s.Authorization.ApplyTo(&c.Authorization); err != nil {
|
if err := s.Authorization.ApplyTo(&c.Authorization); err != nil {
|
||||||
|
|
|
@ -190,7 +190,7 @@ func (o *Options) ApplyTo(c *schedulerappconfig.Config) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if o.SecureServing != nil && (o.SecureServing.BindPort != 0 || o.SecureServing.Listener != nil) {
|
if o.SecureServing != nil && (o.SecureServing.BindPort != 0 || o.SecureServing.Listener != nil) {
|
||||||
if err := o.Authentication.ApplyTo(&c.Authentication, c.SecureServing, nil); err != nil {
|
if err := o.Authentication.ApplyTo(&c.Authentication, c.SecureServing); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := o.Authorization.ApplyTo(&c.Authorization); err != nil {
|
if err := o.Authorization.ApplyTo(&c.Authorization); err != nil {
|
||||||
|
|
|
@ -76,8 +76,7 @@ func BuildAuthn(client authenticationclient.TokenReviewInterface, authn kubeletc
|
||||||
authenticatorConfig.TokenAccessReviewClient = client
|
authenticatorConfig.TokenAccessReviewClient = client
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticator, _, err := authenticatorConfig.New()
|
return authenticatorConfig.New()
|
||||||
return authenticator, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildAuthz creates an authorizer compatible with the kubelet's needs
|
// BuildAuthz creates an authorizer compatible with the kubelet's needs
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
# doc.go is managed by kazel, so gazelle should ignore it.
|
|
||||||
# gazelle:exclude doc.go
|
|
||||||
|
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
load("//pkg/generated/openapi:def.bzl", "openapi_library")
|
|
||||||
load("//build:openapi.bzl", "openapi_go_prefix", "openapi_vendor_prefix")
|
|
||||||
|
|
||||||
openapi_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["doc.go"],
|
|
||||||
go_prefix = openapi_go_prefix,
|
|
||||||
openapi_targets = [
|
|
||||||
"cmd/cloud-controller-manager/app/apis/config/v1alpha1",
|
|
||||||
"pkg/apis/abac/v0",
|
|
||||||
"pkg/apis/abac/v1beta1",
|
|
||||||
"pkg/apis/auditregistration",
|
|
||||||
"pkg/version",
|
|
||||||
],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
vendor_prefix = openapi_vendor_prefix,
|
|
||||||
vendor_targets = [
|
|
||||||
"k8s.io/api/admission/v1beta1",
|
|
||||||
"k8s.io/api/admissionregistration/v1alpha1",
|
|
||||||
"k8s.io/api/admissionregistration/v1beta1",
|
|
||||||
"k8s.io/api/apps/v1",
|
|
||||||
"k8s.io/api/apps/v1beta1",
|
|
||||||
"k8s.io/api/apps/v1beta2",
|
|
||||||
"k8s.io/api/auditregistration/v1alpha1",
|
|
||||||
"k8s.io/api/authentication/v1",
|
|
||||||
"k8s.io/api/authentication/v1beta1",
|
|
||||||
"k8s.io/api/authorization/v1",
|
|
||||||
"k8s.io/api/authorization/v1beta1",
|
|
||||||
"k8s.io/api/autoscaling/v1",
|
|
||||||
"k8s.io/api/autoscaling/v2beta1",
|
|
||||||
"k8s.io/api/autoscaling/v2beta2",
|
|
||||||
"k8s.io/api/batch/v1",
|
|
||||||
"k8s.io/api/batch/v1beta1",
|
|
||||||
"k8s.io/api/batch/v2alpha1",
|
|
||||||
"k8s.io/api/certificates/v1beta1",
|
|
||||||
"k8s.io/api/coordination/v1beta1",
|
|
||||||
"k8s.io/api/core/v1",
|
|
||||||
"k8s.io/api/events/v1beta1",
|
|
||||||
"k8s.io/api/extensions/v1beta1",
|
|
||||||
"k8s.io/api/imagepolicy/v1alpha1",
|
|
||||||
"k8s.io/api/networking/v1",
|
|
||||||
"k8s.io/api/policy/v1beta1",
|
|
||||||
"k8s.io/api/rbac/v1",
|
|
||||||
"k8s.io/api/rbac/v1alpha1",
|
|
||||||
"k8s.io/api/rbac/v1beta1",
|
|
||||||
"k8s.io/api/scheduling/v1alpha1",
|
|
||||||
"k8s.io/api/scheduling/v1beta1",
|
|
||||||
"k8s.io/api/settings/v1alpha1",
|
|
||||||
"k8s.io/api/storage/v1",
|
|
||||||
"k8s.io/api/storage/v1alpha1",
|
|
||||||
"k8s.io/api/storage/v1beta1",
|
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1",
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource",
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1",
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1beta1",
|
|
||||||
"k8s.io/apimachinery/pkg/apis/testapigroup/v1",
|
|
||||||
"k8s.io/apimachinery/pkg/runtime",
|
|
||||||
"k8s.io/apimachinery/pkg/util/intstr",
|
|
||||||
"k8s.io/apimachinery/pkg/version",
|
|
||||||
"k8s.io/apiserver/pkg/apis/audit/v1",
|
|
||||||
"k8s.io/apiserver/pkg/apis/audit/v1alpha1",
|
|
||||||
"k8s.io/apiserver/pkg/apis/audit/v1beta1",
|
|
||||||
"k8s.io/apiserver/pkg/apis/example/v1",
|
|
||||||
"k8s.io/apiserver/pkg/apis/example2/v1",
|
|
||||||
"k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1",
|
|
||||||
"k8s.io/client-go/pkg/apis/clientauthentication/v1beta1",
|
|
||||||
"k8s.io/client-go/pkg/version",
|
|
||||||
"k8s.io/csi-api/pkg/apis/csi/v1alpha1",
|
|
||||||
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1",
|
|
||||||
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1",
|
|
||||||
"k8s.io/kube-controller-manager/config/v1alpha1",
|
|
||||||
"k8s.io/kube-proxy/config/v1alpha1",
|
|
||||||
"k8s.io/kube-scheduler/config/v1alpha1",
|
|
||||||
"k8s.io/kubelet/config/v1beta1",
|
|
||||||
"k8s.io/metrics/pkg/apis/custom_metrics/v1beta1",
|
|
||||||
"k8s.io/metrics/pkg/apis/custom_metrics/v1beta2",
|
|
||||||
"k8s.io/metrics/pkg/apis/external_metrics/v1beta1",
|
|
||||||
"k8s.io/metrics/pkg/apis/metrics/v1alpha1",
|
|
||||||
"k8s.io/metrics/pkg/apis/metrics/v1beta1",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
|
@ -1,53 +0,0 @@
|
||||||
# Copyright 2017 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.
|
|
||||||
|
|
||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
|
||||||
load("@io_kubernetes_build//defs:go.bzl", "go_genrule")
|
|
||||||
|
|
||||||
def openapi_library(name, tags, srcs, go_prefix, vendor_prefix = "", openapi_targets = [], vendor_targets = []):
|
|
||||||
deps = [
|
|
||||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
|
|
||||||
] + ["//%s:go_default_library" % target for target in openapi_targets] + ["//staging/src/%s:go_default_library" % target for target in vendor_targets]
|
|
||||||
go_library(
|
|
||||||
name = name,
|
|
||||||
srcs = srcs + [":zz_generated.openapi"],
|
|
||||||
importpath = go_prefix + "pkg/generated/openapi",
|
|
||||||
tags = tags,
|
|
||||||
deps = deps,
|
|
||||||
)
|
|
||||||
go_genrule(
|
|
||||||
name = "zz_generated.openapi",
|
|
||||||
srcs = ["//" + vendor_prefix + "hack/boilerplate:boilerplate.go.txt"],
|
|
||||||
outs = ["zz_generated.openapi.go"],
|
|
||||||
# In order for vendored dependencies to be imported correctly,
|
|
||||||
# the generator must run from the repo root inside the generated GOPATH.
|
|
||||||
# All of bazel's $(location)s are relative to the original working directory, however,
|
|
||||||
# so we must save it first.
|
|
||||||
cmd = " ".join([
|
|
||||||
"cd $$GOPATH/src/" + go_prefix + ";",
|
|
||||||
"$$GO_GENRULE_EXECROOT/$(location //vendor/k8s.io/kube-openapi/cmd/openapi-gen)",
|
|
||||||
"--v 1",
|
|
||||||
"--logtostderr",
|
|
||||||
"--go-header-file $$GO_GENRULE_EXECROOT/$(location //" + vendor_prefix + "hack/boilerplate:boilerplate.go.txt)",
|
|
||||||
"--output-file-base zz_generated.openapi",
|
|
||||||
"--output-package " + go_prefix + "pkg/generated/openapi",
|
|
||||||
"--report-filename tmp_api_violations.report",
|
|
||||||
"--input-dirs " + ",".join([go_prefix + target for target in openapi_targets] + [go_prefix + "vendor/" + target for target in vendor_targets]),
|
|
||||||
"&& cp $$GOPATH/src/" + go_prefix + "pkg/generated/openapi/zz_generated.openapi.go $$GO_GENRULE_EXECROOT/$(location :zz_generated.openapi.go)",
|
|
||||||
"&& rm tmp_api_violations.report",
|
|
||||||
]),
|
|
||||||
go_deps = deps,
|
|
||||||
tools = ["//vendor/k8s.io/kube-openapi/cmd/openapi-gen"],
|
|
||||||
)
|
|
|
@ -1,18 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// openapi generated definitions.
|
|
||||||
package openapi
|
|
|
@ -19,8 +19,6 @@ package authenticator
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
|
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
|
||||||
"k8s.io/apiserver/pkg/authentication/group"
|
"k8s.io/apiserver/pkg/authentication/group"
|
||||||
|
@ -80,10 +78,9 @@ type Config struct {
|
||||||
|
|
||||||
// New returns an authenticator.Request or an error that supports the standard
|
// New returns an authenticator.Request or an error that supports the standard
|
||||||
// Kubernetes authentication mechanisms.
|
// Kubernetes authentication mechanisms.
|
||||||
func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, error) {
|
func (config Config) New() (authenticator.Request, error) {
|
||||||
var authenticators []authenticator.Request
|
var authenticators []authenticator.Request
|
||||||
var tokenAuthenticators []authenticator.Token
|
var tokenAuthenticators []authenticator.Token
|
||||||
securityDefinitions := spec.SecurityDefinitions{}
|
|
||||||
|
|
||||||
// front-proxy, BasicAuth methods, local first, then remote
|
// front-proxy, BasicAuth methods, local first, then remote
|
||||||
// Add the front proxy authenticator if requested
|
// Add the front proxy authenticator if requested
|
||||||
|
@ -96,7 +93,7 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
|
||||||
config.RequestHeaderConfig.ExtraHeaderPrefixes,
|
config.RequestHeaderConfig.ExtraHeaderPrefixes,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, requestHeaderAuthenticator))
|
authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, requestHeaderAuthenticator))
|
||||||
}
|
}
|
||||||
|
@ -105,23 +102,16 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
|
||||||
if len(config.BasicAuthFile) > 0 {
|
if len(config.BasicAuthFile) > 0 {
|
||||||
basicAuth, err := newAuthenticatorFromBasicAuthFile(config.BasicAuthFile)
|
basicAuth, err := newAuthenticatorFromBasicAuthFile(config.BasicAuthFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, basicAuth))
|
authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, basicAuth))
|
||||||
|
|
||||||
securityDefinitions["HTTPBasic"] = &spec.SecurityScheme{
|
|
||||||
SecuritySchemeProps: spec.SecuritySchemeProps{
|
|
||||||
Type: "basic",
|
|
||||||
Description: "HTTP Basic authentication",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// X509 methods
|
// X509 methods
|
||||||
if len(config.ClientCAFile) > 0 {
|
if len(config.ClientCAFile) > 0 {
|
||||||
certAuth, err := newAuthenticatorFromClientCAFile(config.ClientCAFile)
|
certAuth, err := newAuthenticatorFromClientCAFile(config.ClientCAFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
authenticators = append(authenticators, certAuth)
|
authenticators = append(authenticators, certAuth)
|
||||||
}
|
}
|
||||||
|
@ -130,21 +120,21 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
|
||||||
if len(config.TokenAuthFile) > 0 {
|
if len(config.TokenAuthFile) > 0 {
|
||||||
tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile)
|
tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, tokenAuth))
|
tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, tokenAuth))
|
||||||
}
|
}
|
||||||
if len(config.ServiceAccountKeyFiles) > 0 {
|
if len(config.ServiceAccountKeyFiles) > 0 {
|
||||||
serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.APIAudiences, config.ServiceAccountTokenGetter)
|
serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.APIAudiences, config.ServiceAccountTokenGetter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
|
tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
|
||||||
}
|
}
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) && config.ServiceAccountIssuer != "" {
|
if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) && config.ServiceAccountIssuer != "" {
|
||||||
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuer, config.ServiceAccountKeyFiles, config.APIAudiences, config.ServiceAccountTokenGetter)
|
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuer, config.ServiceAccountKeyFiles, config.APIAudiences, config.ServiceAccountTokenGetter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
|
tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
|
||||||
}
|
}
|
||||||
|
@ -174,14 +164,14 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
|
||||||
RequiredClaims: config.OIDCRequiredClaims,
|
RequiredClaims: config.OIDCRequiredClaims,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tokenAuthenticators = append(tokenAuthenticators, oidcAuth)
|
tokenAuthenticators = append(tokenAuthenticators, oidcAuth)
|
||||||
}
|
}
|
||||||
if len(config.WebhookTokenAuthnConfigFile) > 0 {
|
if len(config.WebhookTokenAuthnConfigFile) > 0 {
|
||||||
webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnCacheTTL, config.APIAudiences)
|
webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnCacheTTL, config.APIAudiences)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuth)
|
tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuth)
|
||||||
}
|
}
|
||||||
|
@ -194,21 +184,13 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
|
||||||
tokenAuth = tokencache.New(tokenAuth, true, config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL)
|
tokenAuth = tokencache.New(tokenAuth, true, config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL)
|
||||||
}
|
}
|
||||||
authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth))
|
authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth))
|
||||||
securityDefinitions["BearerToken"] = &spec.SecurityScheme{
|
|
||||||
SecuritySchemeProps: spec.SecuritySchemeProps{
|
|
||||||
Type: "apiKey",
|
|
||||||
Name: "authorization",
|
|
||||||
In: "header",
|
|
||||||
Description: "Bearer Token authentication",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(authenticators) == 0 {
|
if len(authenticators) == 0 {
|
||||||
if config.Anonymous {
|
if config.Anonymous {
|
||||||
return anonymous.NewAuthenticator(), &securityDefinitions, nil
|
return anonymous.NewAuthenticator(), nil
|
||||||
}
|
}
|
||||||
return nil, &securityDefinitions, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticator := union.New(authenticators...)
|
authenticator := union.New(authenticators...)
|
||||||
|
@ -221,7 +203,7 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
|
||||||
authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
|
authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
|
||||||
}
|
}
|
||||||
|
|
||||||
return authenticator, &securityDefinitions, nil
|
return authenticator, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file
|
// IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = [
|
|
||||||
"factory.go",
|
|
||||||
"item.go",
|
|
||||||
"list_element.go",
|
|
||||||
"map_element.go",
|
|
||||||
"openapi.go",
|
|
||||||
"primitive_element.go",
|
|
||||||
"type_element.go",
|
|
||||||
"util.go",
|
|
||||||
"visitor.go",
|
|
||||||
],
|
|
||||||
importpath = "k8s.io/kubernetes/pkg/kubectl/apply/parse",
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
deps = [
|
|
||||||
"//pkg/kubectl/apply:go_default_library",
|
|
||||||
"//pkg/kubectl/cmd/util/openapi:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = ["suite_test.go"],
|
|
||||||
data = [
|
|
||||||
"//api/openapi-spec:swagger-spec",
|
|
||||||
],
|
|
||||||
embed = [":go_default_library"],
|
|
||||||
deps = [
|
|
||||||
"//vendor/github.com/onsi/ginkgo:go_default_library",
|
|
||||||
"//vendor/github.com/onsi/ginkgo/config:go_default_library",
|
|
||||||
"//vendor/github.com/onsi/ginkgo/types:go_default_library",
|
|
||||||
"//vendor/github.com/onsi/gomega:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
|
@ -1,120 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/apply"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Factory creates an Element by combining object values from recorded, local and remote sources with
|
|
||||||
// the metadata from an openapi schema.
|
|
||||||
type Factory struct {
|
|
||||||
// Resources contains the openapi field metadata for the object models
|
|
||||||
Resources openapi.Resources
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateElement returns an Element by collating the recorded, local and remote field values
|
|
||||||
func (b *Factory) CreateElement(recorded, local, remote map[string]interface{}) (apply.Element, error) {
|
|
||||||
// Create an Item from the 3 values. Use empty name for field
|
|
||||||
visitor := &ElementBuildingVisitor{b.Resources}
|
|
||||||
|
|
||||||
gvk, err := getCommonGroupVersionKind(recorded, local, remote)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the openapi object metadata
|
|
||||||
s := visitor.resources.LookupResource(gvk)
|
|
||||||
oapiKind, err := getKind(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data := apply.NewRawElementData(recorded, local, remote)
|
|
||||||
fieldName := ""
|
|
||||||
item, err := visitor.getItem(oapiKind, fieldName, data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collate each field of the item into a combined Element
|
|
||||||
return item.CreateElement(visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getItem returns the appropriate Item based on the underlying type of the arguments
|
|
||||||
func (v *ElementBuildingVisitor) getItem(s proto.Schema, name string, data apply.RawElementData) (Item, error) {
|
|
||||||
kind, err := getType(data.GetRecorded(), data.GetLocal(), data.GetRemote())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if kind == nil {
|
|
||||||
// All of the items values are nil.
|
|
||||||
return &emptyItem{Name: name}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an item matching the type
|
|
||||||
switch kind.Kind() {
|
|
||||||
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint,
|
|
||||||
reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64,
|
|
||||||
reflect.String:
|
|
||||||
p, err := getPrimitive(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("expected openapi Primitive, was %T for %v (%v)", s, kind, err)
|
|
||||||
}
|
|
||||||
return &primitiveItem{name, p, data}, nil
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
a, err := getArray(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("expected openapi Array, was %T for %v (%v)", s, kind, err)
|
|
||||||
}
|
|
||||||
return &listItem{
|
|
||||||
Name: name,
|
|
||||||
Array: a,
|
|
||||||
ListElementData: apply.ListElementData{
|
|
||||||
RawElementData: data,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
case reflect.Map:
|
|
||||||
if k, err := getKind(s); err == nil {
|
|
||||||
return &typeItem{
|
|
||||||
Name: name,
|
|
||||||
Type: k,
|
|
||||||
MapElementData: apply.MapElementData{
|
|
||||||
RawElementData: data,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
// If it looks like a map, and no openapi type is found, default to mapItem
|
|
||||||
m, err := getMap(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("expected openapi Kind or Map, was %T for %v (%v)", s, kind, err)
|
|
||||||
}
|
|
||||||
return &mapItem{
|
|
||||||
Name: name,
|
|
||||||
Map: m,
|
|
||||||
MapElementData: apply.MapElementData{
|
|
||||||
RawElementData: data,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unsupported type %v", kind)
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/apply"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Item wraps values from 3 sources (recorded, local, remote).
|
|
||||||
// The values are not collated
|
|
||||||
type Item interface {
|
|
||||||
// CreateElement merges the values in the item into a combined Element
|
|
||||||
CreateElement(ItemVisitor) (apply.Element, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// primitiveItem contains a recorded, local, and remote value
|
|
||||||
type primitiveItem struct {
|
|
||||||
Name string
|
|
||||||
Primitive *proto.Primitive
|
|
||||||
|
|
||||||
apply.RawElementData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *primitiveItem) CreateElement(v ItemVisitor) (apply.Element, error) {
|
|
||||||
return v.CreatePrimitiveElement(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *primitiveItem) GetMeta() proto.Schema {
|
|
||||||
// https://golang.org/doc/faq#nil_error
|
|
||||||
if i.Primitive != nil {
|
|
||||||
return i.Primitive
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// listItem contains a recorded, local, and remote list
|
|
||||||
type listItem struct {
|
|
||||||
Name string
|
|
||||||
Array *proto.Array
|
|
||||||
|
|
||||||
apply.ListElementData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *listItem) CreateElement(v ItemVisitor) (apply.Element, error) {
|
|
||||||
return v.CreateListElement(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *listItem) GetMeta() proto.Schema {
|
|
||||||
// https://golang.org/doc/faq#nil_error
|
|
||||||
if i.Array != nil {
|
|
||||||
return i.Array
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mapItem contains a recorded, local, and remote map
|
|
||||||
type mapItem struct {
|
|
||||||
Name string
|
|
||||||
Map *proto.Map
|
|
||||||
|
|
||||||
apply.MapElementData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *mapItem) CreateElement(v ItemVisitor) (apply.Element, error) {
|
|
||||||
return v.CreateMapElement(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *mapItem) GetMeta() proto.Schema {
|
|
||||||
// https://golang.org/doc/faq#nil_error
|
|
||||||
if i.Map != nil {
|
|
||||||
return i.Map
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mapItem contains a recorded, local, and remote map
|
|
||||||
type typeItem struct {
|
|
||||||
Name string
|
|
||||||
Type *proto.Kind
|
|
||||||
|
|
||||||
apply.MapElementData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *typeItem) GetMeta() proto.Schema {
|
|
||||||
// https://golang.org/doc/faq#nil_error
|
|
||||||
if i.Type != nil {
|
|
||||||
return i.Type
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *typeItem) CreateElement(v ItemVisitor) (apply.Element, error) {
|
|
||||||
return v.CreateTypeElement(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// emptyItem contains no values
|
|
||||||
type emptyItem struct {
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *emptyItem) CreateElement(v ItemVisitor) (apply.Element, error) {
|
|
||||||
e := &apply.EmptyElement{}
|
|
||||||
e.Name = i.Name
|
|
||||||
return e, nil
|
|
||||||
}
|
|
|
@ -1,199 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/apply"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Contains the heavy lifting for finding tuples of matching elements in lists based on the merge key
|
|
||||||
// and then uses the canonical order derived from the orders in the recorded, local and remote lists.
|
|
||||||
|
|
||||||
// replaceListElement builds a ListElement for a listItem.
|
|
||||||
// Uses the "merge" strategy to identify "same" elements across lists by a "merge key"
|
|
||||||
func (v ElementBuildingVisitor) mergeListElement(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
|
|
||||||
subtype := getSchemaType(item.Array.SubType)
|
|
||||||
switch subtype {
|
|
||||||
case "primitive":
|
|
||||||
return v.doPrimitiveList(meta, item)
|
|
||||||
case "map", "kind", "reference":
|
|
||||||
return v.doMapList(meta, item)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("Cannot merge lists with subtype %s", subtype)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// doPrimitiveList merges 3 lists of primitives together
|
|
||||||
// tries to maintain ordering
|
|
||||||
func (v ElementBuildingVisitor) doPrimitiveList(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
|
|
||||||
result := &apply.ListElement{
|
|
||||||
FieldMetaImpl: apply.FieldMetaImpl{
|
|
||||||
MergeType: apply.MergeStrategy,
|
|
||||||
Name: item.Name,
|
|
||||||
},
|
|
||||||
ListElementData: item.ListElementData,
|
|
||||||
Values: []apply.Element{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use locally defined order, then add remote, then add recorded.
|
|
||||||
orderedKeys := &apply.CombinedPrimitiveSlice{}
|
|
||||||
|
|
||||||
// Locally defined items come first and retain their order
|
|
||||||
// as defined locally
|
|
||||||
for _, l := range item.GetLocalList() {
|
|
||||||
orderedKeys.UpsertLocal(l)
|
|
||||||
}
|
|
||||||
// Mixin remote values, adding any that are not present locally
|
|
||||||
for _, l := range item.GetRemoteList() {
|
|
||||||
orderedKeys.UpsertRemote(l)
|
|
||||||
}
|
|
||||||
// Mixin recorded values, adding any that are not present locally
|
|
||||||
// or remotely
|
|
||||||
for _, l := range item.GetRecordedList() {
|
|
||||||
orderedKeys.UpsertRecorded(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, l := range orderedKeys.Items {
|
|
||||||
var s proto.Schema
|
|
||||||
if item.Array != nil && item.Array.SubType != nil {
|
|
||||||
s = item.Array.SubType
|
|
||||||
}
|
|
||||||
|
|
||||||
subitem, err := v.getItem(s, fmt.Sprintf("%d", i), l.RawElementData)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the Item to an Element
|
|
||||||
newelem, err := subitem.CreateElement(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append the element to the list
|
|
||||||
result.Values = append(result.Values, newelem)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// doMapList merges 3 lists of maps together by collating their values.
|
|
||||||
// tries to retain ordering
|
|
||||||
func (v ElementBuildingVisitor) doMapList(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
|
|
||||||
key := meta.GetFieldMergeKeys()
|
|
||||||
result := &apply.ListElement{
|
|
||||||
FieldMetaImpl: apply.FieldMetaImpl{
|
|
||||||
MergeType: apply.MergeStrategy,
|
|
||||||
MergeKeys: key,
|
|
||||||
Name: item.Name,
|
|
||||||
},
|
|
||||||
ListElementData: item.ListElementData,
|
|
||||||
Values: []apply.Element{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use locally defined order, then add remote, then add recorded.
|
|
||||||
orderedKeys := &apply.CombinedMapSlice{}
|
|
||||||
|
|
||||||
// Locally defined items come first and retain their order
|
|
||||||
// as defined locally
|
|
||||||
for _, l := range item.GetLocalList() {
|
|
||||||
orderedKeys.UpsertLocal(key, l)
|
|
||||||
}
|
|
||||||
// Mixin remote values, adding any that are not present locally
|
|
||||||
for _, l := range item.GetRemoteList() {
|
|
||||||
orderedKeys.UpsertRemote(key, l)
|
|
||||||
}
|
|
||||||
// Mixin recorded values, adding any that are not present locally
|
|
||||||
// or remotely
|
|
||||||
for _, l := range item.GetRecordedList() {
|
|
||||||
orderedKeys.UpsertRecorded(key, l)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, l := range orderedKeys.Items {
|
|
||||||
var s proto.Schema
|
|
||||||
if item.Array != nil && item.Array.SubType != nil {
|
|
||||||
s = item.Array.SubType
|
|
||||||
}
|
|
||||||
subitem, err := v.getItem(s, fmt.Sprintf("%d", i), l.RawElementData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the element fully
|
|
||||||
newelem, err := subitem.CreateElement(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append the element to the list
|
|
||||||
result.Values = append(result.Values, newelem)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// replaceListElement builds a new ListElement from a listItem
|
|
||||||
// Uses the "replace" strategy and identify "same" elements across lists by their index
|
|
||||||
func (v ElementBuildingVisitor) replaceListElement(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
|
|
||||||
meta.Name = item.Name
|
|
||||||
result := &apply.ListElement{
|
|
||||||
FieldMetaImpl: meta,
|
|
||||||
ListElementData: item.ListElementData,
|
|
||||||
Values: []apply.Element{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the max length to iterate over the slices
|
|
||||||
for i := 0; i < max(len(item.GetRecordedList()), len(item.GetLocalList()), len(item.GetRemoteList())); i++ {
|
|
||||||
|
|
||||||
// Lookup the item from each list
|
|
||||||
data := apply.RawElementData{}
|
|
||||||
if recorded, recordedSet := boundsSafeLookup(i, item.GetRecordedList()); recordedSet {
|
|
||||||
data.SetRecorded(recorded)
|
|
||||||
}
|
|
||||||
if local, localSet := boundsSafeLookup(i, item.GetLocalList()); localSet {
|
|
||||||
data.SetLocal(local)
|
|
||||||
}
|
|
||||||
if remote, remoteSet := boundsSafeLookup(i, item.GetRemoteList()); remoteSet {
|
|
||||||
data.SetRemote(remote)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the Item
|
|
||||||
var s proto.Schema
|
|
||||||
if item.Array != nil && item.Array.SubType != nil {
|
|
||||||
s = item.Array.SubType
|
|
||||||
}
|
|
||||||
subitem, err := v.getItem(s, fmt.Sprintf("%d", i), data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the element
|
|
||||||
newelem, err := subitem.CreateElement(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append the element to the list
|
|
||||||
result.Values = append(result.Values, newelem)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/apply"
|
|
||||||
)
|
|
||||||
|
|
||||||
// mapElement builds a new mapElement from a mapItem
|
|
||||||
func (v ElementBuildingVisitor) mapElement(meta apply.FieldMetaImpl, item *mapItem) (*apply.MapElement, error) {
|
|
||||||
// Function to return schema type of the map values
|
|
||||||
var fn schemaFn = func(string) proto.Schema {
|
|
||||||
// All map values share the same schema
|
|
||||||
if item.Map != nil && item.Map.SubType != nil {
|
|
||||||
return item.Map.SubType
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect same fields from multiple maps into a map of elements
|
|
||||||
values, err := v.createMapValues(fn, meta, item.MapElementData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the result
|
|
||||||
return &apply.MapElement{
|
|
||||||
FieldMetaImpl: meta,
|
|
||||||
MapElementData: item.MapElementData,
|
|
||||||
Values: values,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// schemaFn returns the schema for a field or map value based on its name or key
|
|
||||||
type schemaFn func(key string) proto.Schema
|
|
||||||
|
|
||||||
// createMapValues combines the recorded, local and remote values from
|
|
||||||
// data into a map of elements.
|
|
||||||
func (v ElementBuildingVisitor) createMapValues(
|
|
||||||
schemaFn schemaFn,
|
|
||||||
meta apply.FieldMetaImpl,
|
|
||||||
data apply.MapElementData) (map[string]apply.Element, error) {
|
|
||||||
|
|
||||||
// Collate each key in the map
|
|
||||||
values := map[string]apply.Element{}
|
|
||||||
for _, key := range keysUnion(data.GetRecordedMap(), data.GetLocalMap(), data.GetRemoteMap()) {
|
|
||||||
combined := apply.RawElementData{}
|
|
||||||
if recorded, recordedSet := nilSafeLookup(key, data.GetRecordedMap()); recordedSet {
|
|
||||||
combined.SetRecorded(recorded)
|
|
||||||
}
|
|
||||||
if local, localSet := nilSafeLookup(key, data.GetLocalMap()); localSet {
|
|
||||||
combined.SetLocal(local)
|
|
||||||
}
|
|
||||||
if remote, remoteSet := nilSafeLookup(key, data.GetRemoteMap()); remoteSet {
|
|
||||||
combined.SetRemote(remote)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an item for the field
|
|
||||||
field, err := v.getItem(schemaFn(key), key, combined)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the element for this field
|
|
||||||
element, err := field.CreateElement(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the field element to the map
|
|
||||||
values[key] = element
|
|
||||||
}
|
|
||||||
return values, nil
|
|
||||||
}
|
|
|
@ -1,227 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Contains functions for casting openapi interfaces to their underlying types
|
|
||||||
|
|
||||||
// getSchemaType returns the string type of the schema - e.g. array, primitive, map, kind, reference
|
|
||||||
func getSchemaType(schema proto.Schema) string {
|
|
||||||
if schema == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
visitor := &baseSchemaVisitor{}
|
|
||||||
schema.Accept(visitor)
|
|
||||||
return visitor.Kind
|
|
||||||
}
|
|
||||||
|
|
||||||
// getKind converts schema to an *proto.Kind object
|
|
||||||
func getKind(schema proto.Schema) (*proto.Kind, error) {
|
|
||||||
if schema == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
visitor := &kindSchemaVisitor{}
|
|
||||||
schema.Accept(visitor)
|
|
||||||
return visitor.Result, visitor.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// getArray converts schema to an *proto.Array object
|
|
||||||
func getArray(schema proto.Schema) (*proto.Array, error) {
|
|
||||||
if schema == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
visitor := &arraySchemaVisitor{}
|
|
||||||
schema.Accept(visitor)
|
|
||||||
return visitor.Result, visitor.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// getMap converts schema to an *proto.Map object
|
|
||||||
func getMap(schema proto.Schema) (*proto.Map, error) {
|
|
||||||
if schema == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
visitor := &mapSchemaVisitor{}
|
|
||||||
schema.Accept(visitor)
|
|
||||||
return visitor.Result, visitor.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPrimitive converts schema to an *proto.Primitive object
|
|
||||||
func getPrimitive(schema proto.Schema) (*proto.Primitive, error) {
|
|
||||||
if schema == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
visitor := &primitiveSchemaVisitor{}
|
|
||||||
schema.Accept(visitor)
|
|
||||||
return visitor.Result, visitor.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
type baseSchemaVisitor struct {
|
|
||||||
Err error
|
|
||||||
Kind string
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitArray implements openapi
|
|
||||||
func (v *baseSchemaVisitor) VisitArray(array *proto.Array) {
|
|
||||||
v.Kind = "array"
|
|
||||||
v.Err = fmt.Errorf("Array type not expected")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergeMap implements openapi
|
|
||||||
func (v *baseSchemaVisitor) VisitMap(*proto.Map) {
|
|
||||||
v.Kind = "map"
|
|
||||||
v.Err = fmt.Errorf("Map type not expected")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergePrimitive implements openapi
|
|
||||||
func (v *baseSchemaVisitor) VisitPrimitive(*proto.Primitive) {
|
|
||||||
v.Kind = "primitive"
|
|
||||||
v.Err = fmt.Errorf("Primitive type not expected")
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitKind implements openapi
|
|
||||||
func (v *baseSchemaVisitor) VisitKind(*proto.Kind) {
|
|
||||||
v.Kind = "kind"
|
|
||||||
v.Err = fmt.Errorf("Kind type not expected")
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitReference implements openapi
|
|
||||||
func (v *baseSchemaVisitor) VisitReference(reference proto.Reference) {
|
|
||||||
v.Kind = "reference"
|
|
||||||
v.Err = fmt.Errorf("Reference type not expected")
|
|
||||||
}
|
|
||||||
|
|
||||||
type kindSchemaVisitor struct {
|
|
||||||
baseSchemaVisitor
|
|
||||||
Result *proto.Kind
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitKind implements openapi
|
|
||||||
func (v *kindSchemaVisitor) VisitKind(result *proto.Kind) {
|
|
||||||
v.Result = result
|
|
||||||
v.Kind = "kind"
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitReference implements openapi
|
|
||||||
func (v *kindSchemaVisitor) VisitReference(reference proto.Reference) {
|
|
||||||
reference.SubSchema().Accept(v)
|
|
||||||
if v.Err == nil {
|
|
||||||
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyExtensions(field string, from, to map[string]interface{}) error {
|
|
||||||
// Copy extensions from field to type for references
|
|
||||||
for key, val := range from {
|
|
||||||
if curr, found := to[key]; found {
|
|
||||||
// Don't allow the same extension to be defined both on the field and on the type
|
|
||||||
return fmt.Errorf("Cannot override value for extension %s on field %s from %v to %v",
|
|
||||||
key, field, curr, val)
|
|
||||||
}
|
|
||||||
to[key] = val
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type mapSchemaVisitor struct {
|
|
||||||
baseSchemaVisitor
|
|
||||||
Result *proto.Map
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergeMap implements openapi
|
|
||||||
func (v *mapSchemaVisitor) VisitMap(result *proto.Map) {
|
|
||||||
v.Result = result
|
|
||||||
v.Kind = "map"
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitReference implements openapi
|
|
||||||
func (v *mapSchemaVisitor) VisitReference(reference proto.Reference) {
|
|
||||||
reference.SubSchema().Accept(v)
|
|
||||||
if v.Err == nil {
|
|
||||||
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type arraySchemaVisitor struct {
|
|
||||||
baseSchemaVisitor
|
|
||||||
Result *proto.Array
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitArray implements openapi
|
|
||||||
func (v *arraySchemaVisitor) VisitArray(result *proto.Array) {
|
|
||||||
v.Result = result
|
|
||||||
v.Kind = "array"
|
|
||||||
v.Err = copySubElementPatchStrategy(result.Path.String(), result.GetExtensions(), result.SubType.GetExtensions())
|
|
||||||
}
|
|
||||||
|
|
||||||
// copyPatchStrategy copies the strategies to subelements to the subtype
|
|
||||||
// e.g. PodTemplate.Volumes is a []Volume with "x-kubernetes-patch-strategy": "merge,retainKeys"
|
|
||||||
// the "retainKeys" strategy applies to merging Volumes, and must be copied to the sub element
|
|
||||||
func copySubElementPatchStrategy(field string, from, to map[string]interface{}) error {
|
|
||||||
// Check if the parent has a patch strategy extension
|
|
||||||
if ext, found := from["x-kubernetes-patch-strategy"]; found {
|
|
||||||
strategy, ok := ext.(string)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("Expected string value for x-kubernetes-patch-strategy on %s, was %T",
|
|
||||||
field, ext)
|
|
||||||
}
|
|
||||||
// Check of the parent patch strategy has a sub patch strategy, and if so copy to the sub type
|
|
||||||
if strings.Contains(strategy, ",") {
|
|
||||||
strategies := strings.Split(strategy, ",")
|
|
||||||
if len(strategies) != 2 {
|
|
||||||
// Only 1 sub strategy is supported
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Expected between 0 and 2 elements for x-kubernetes-patch-merge-strategy by got %v",
|
|
||||||
strategies)
|
|
||||||
}
|
|
||||||
to["x-kubernetes-patch-strategy"] = strategies[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergePrimitive implements openapi
|
|
||||||
func (v *arraySchemaVisitor) VisitReference(reference proto.Reference) {
|
|
||||||
reference.SubSchema().Accept(v)
|
|
||||||
if v.Err == nil {
|
|
||||||
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type primitiveSchemaVisitor struct {
|
|
||||||
baseSchemaVisitor
|
|
||||||
Result *proto.Primitive
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergePrimitive implements openapi
|
|
||||||
func (v *primitiveSchemaVisitor) VisitPrimitive(result *proto.Primitive) {
|
|
||||||
v.Result = result
|
|
||||||
v.Kind = "primitive"
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitReference implements openapi
|
|
||||||
func (v *primitiveSchemaVisitor) VisitReference(reference proto.Reference) {
|
|
||||||
reference.SubSchema().Accept(v)
|
|
||||||
if v.Err == nil {
|
|
||||||
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 parse
|
|
||||||
|
|
||||||
import "k8s.io/kubernetes/pkg/kubectl/apply"
|
|
||||||
|
|
||||||
// primitiveElement builds a new primitiveElement from a PrimitiveItem
|
|
||||||
func (v ElementBuildingVisitor) primitiveElement(item *primitiveItem) (*apply.PrimitiveElement, error) {
|
|
||||||
meta := apply.FieldMetaImpl{Name: item.Name}
|
|
||||||
return &apply.PrimitiveElement{
|
|
||||||
FieldMetaImpl: meta,
|
|
||||||
RawElementData: item.RawElementData,
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 parse_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/ginkgo/config"
|
|
||||||
. "github.com/onsi/ginkgo/types"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestOpenapi(t *testing.T) {
|
|
||||||
RegisterFailHandler(Fail)
|
|
||||||
RunSpecsWithDefaultAndCustomReporters(t, "Openapi Suite", []Reporter{newlineReporter{}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print a newline after the default newlineReporter due to issue
|
|
||||||
// https://github.com/jstemmer/go-junit-report/issues/31
|
|
||||||
type newlineReporter struct{}
|
|
||||||
|
|
||||||
func (newlineReporter) SpecSuiteWillBegin(config GinkgoConfigType, summary *SuiteSummary) {}
|
|
||||||
|
|
||||||
func (newlineReporter) BeforeSuiteDidRun(setupSummary *SetupSummary) {}
|
|
||||||
|
|
||||||
func (newlineReporter) AfterSuiteDidRun(setupSummary *SetupSummary) {}
|
|
||||||
|
|
||||||
func (newlineReporter) SpecWillRun(specSummary *SpecSummary) {}
|
|
||||||
|
|
||||||
func (newlineReporter) SpecDidComplete(specSummary *SpecSummary) {}
|
|
||||||
|
|
||||||
// SpecSuiteDidEnd Prints a newline between "35 Passed | 0 Failed | 0 Pending | 0 Skipped" and "--- PASS:"
|
|
||||||
func (newlineReporter) SpecSuiteDidEnd(summary *SuiteSummary) { fmt.Printf("\n") }
|
|
|
@ -1,46 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/apply"
|
|
||||||
)
|
|
||||||
|
|
||||||
// typeElement builds a new mapElement from a typeItem
|
|
||||||
func (v ElementBuildingVisitor) typeElement(meta apply.FieldMetaImpl, item *typeItem) (*apply.TypeElement, error) {
|
|
||||||
// Function to get the schema of a field from its key
|
|
||||||
var fn schemaFn = func(key string) proto.Schema {
|
|
||||||
if item.Type != nil && item.Type.Fields != nil {
|
|
||||||
return item.Type.Fields[key]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect same fields from multiple maps into a map of elements
|
|
||||||
values, err := v.createMapValues(fn, meta, item.MapElementData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the result
|
|
||||||
return &apply.TypeElement{
|
|
||||||
FieldMetaImpl: meta,
|
|
||||||
MapElementData: item.MapElementData,
|
|
||||||
Values: values,
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,181 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/apply"
|
|
||||||
)
|
|
||||||
|
|
||||||
// nilSafeLookup returns the value from the map if the map is non-nil
|
|
||||||
func nilSafeLookup(key string, from map[string]interface{}) (interface{}, bool) {
|
|
||||||
if from != nil {
|
|
||||||
value, found := from[key]
|
|
||||||
return value, found
|
|
||||||
}
|
|
||||||
// Not present
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// boundsSafeLookup returns the value from the slice if the slice is non-nil and
|
|
||||||
// the index is in bounds.
|
|
||||||
func boundsSafeLookup(index int, from []interface{}) (interface{}, bool) {
|
|
||||||
if from != nil && len(from) > index {
|
|
||||||
return from[index], true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// keysUnion returns a slice containing the union of the keys present in the arguments
|
|
||||||
func keysUnion(maps ...map[string]interface{}) []string {
|
|
||||||
keys := map[string]interface{}{}
|
|
||||||
for _, m := range maps {
|
|
||||||
for k := range m {
|
|
||||||
keys[k] = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result := []string{}
|
|
||||||
for key := range keys {
|
|
||||||
result = append(result, key)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// max returns the argument with the highest value
|
|
||||||
func max(values ...int) int {
|
|
||||||
v := 0
|
|
||||||
for _, i := range values {
|
|
||||||
if i > v {
|
|
||||||
v = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// getType returns the type of the arguments. If the arguments don't have matching
|
|
||||||
// types, getType returns an error. Nil types matching everything.
|
|
||||||
func getType(args ...interface{}) (reflect.Type, error) {
|
|
||||||
var last interface{}
|
|
||||||
for _, next := range args {
|
|
||||||
// Skip nil values
|
|
||||||
if next == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the first non-nil value we find and continue
|
|
||||||
if last == nil {
|
|
||||||
last = next
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the types of the values match
|
|
||||||
if reflect.TypeOf(last).Kind() != reflect.TypeOf(next).Kind() {
|
|
||||||
return nil, fmt.Errorf("missmatching non-nil types for the same field: %T %T", last, next)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return reflect.TypeOf(last), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFieldMeta parses the metadata about the field from the openapi spec
|
|
||||||
func getFieldMeta(s proto.Schema, name string) (apply.FieldMetaImpl, error) {
|
|
||||||
m := apply.FieldMetaImpl{}
|
|
||||||
if s != nil {
|
|
||||||
ext := s.GetExtensions()
|
|
||||||
if e, found := ext["x-kubernetes-patch-strategy"]; found {
|
|
||||||
strategy, ok := e.(string)
|
|
||||||
if !ok {
|
|
||||||
return apply.FieldMetaImpl{}, fmt.Errorf("Expected string for x-kubernetes-patch-strategy by got %T", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take the first strategy if there are substrategies.
|
|
||||||
// Sub strategies are copied to sub types in openapi.go
|
|
||||||
strategies := strings.Split(strategy, ",")
|
|
||||||
if len(strategies) > 2 {
|
|
||||||
return apply.FieldMetaImpl{}, fmt.Errorf("Expected between 0 and 2 elements for x-kubernetes-patch-merge-strategy by got %v", strategies)
|
|
||||||
}
|
|
||||||
// For lists, choose the strategy for this type, not the subtype
|
|
||||||
m.MergeType = strategies[0]
|
|
||||||
}
|
|
||||||
if k, found := ext["x-kubernetes-patch-merge-key"]; found {
|
|
||||||
key, ok := k.(string)
|
|
||||||
if !ok {
|
|
||||||
return apply.FieldMetaImpl{}, fmt.Errorf("Expected string for x-kubernetes-patch-merge-key by got %T", k)
|
|
||||||
}
|
|
||||||
m.MergeKeys = apply.MergeKeys(strings.Split(key, ","))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.Name = name
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCommonGroupVersionKind verifies that the recorded, local and remote all share
|
|
||||||
// the same GroupVersionKind and returns the value
|
|
||||||
func getCommonGroupVersionKind(recorded, local, remote map[string]interface{}) (schema.GroupVersionKind, error) {
|
|
||||||
recordedGVK, err := getGroupVersionKind(recorded)
|
|
||||||
if err != nil {
|
|
||||||
return schema.GroupVersionKind{}, err
|
|
||||||
}
|
|
||||||
localGVK, err := getGroupVersionKind(local)
|
|
||||||
if err != nil {
|
|
||||||
return schema.GroupVersionKind{}, err
|
|
||||||
}
|
|
||||||
remoteGVK, err := getGroupVersionKind(remote)
|
|
||||||
if err != nil {
|
|
||||||
return schema.GroupVersionKind{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(recordedGVK, localGVK) || !reflect.DeepEqual(localGVK, remoteGVK) {
|
|
||||||
return schema.GroupVersionKind{},
|
|
||||||
fmt.Errorf("group version kinds do not match (recorded: %v local: %v remote: %v)",
|
|
||||||
recordedGVK, localGVK, remoteGVK)
|
|
||||||
}
|
|
||||||
return recordedGVK, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getGroupVersionKind returns the GroupVersionKind of the object
|
|
||||||
func getGroupVersionKind(config map[string]interface{}) (schema.GroupVersionKind, error) {
|
|
||||||
gvk := schema.GroupVersionKind{}
|
|
||||||
if gv, found := config["apiVersion"]; found {
|
|
||||||
casted, ok := gv.(string)
|
|
||||||
if !ok {
|
|
||||||
return gvk, fmt.Errorf("Expected string for apiVersion, found %T", gv)
|
|
||||||
}
|
|
||||||
s := strings.Split(casted, "/")
|
|
||||||
if len(s) != 1 {
|
|
||||||
gvk.Group = s[0]
|
|
||||||
}
|
|
||||||
gvk.Version = s[len(s)-1]
|
|
||||||
} else {
|
|
||||||
return gvk, fmt.Errorf("Missing apiVersion in Kind %v", config)
|
|
||||||
}
|
|
||||||
if k, found := config["kind"]; found {
|
|
||||||
casted, ok := k.(string)
|
|
||||||
if !ok {
|
|
||||||
return gvk, fmt.Errorf("Expected string for kind, found %T", k)
|
|
||||||
}
|
|
||||||
gvk.Kind = casted
|
|
||||||
} else {
|
|
||||||
return gvk, fmt.Errorf("Missing kind in Kind %v", config)
|
|
||||||
}
|
|
||||||
return gvk, nil
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/apply"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ItemVisitor provides an interface for Items to Accept and call
|
|
||||||
// the Visit function that corresponds to its actual type.
|
|
||||||
type ItemVisitor interface {
|
|
||||||
// CreatePrimitiveElement builds an Element for a primitiveItem
|
|
||||||
CreatePrimitiveElement(*primitiveItem) (apply.Element, error)
|
|
||||||
|
|
||||||
// CreateListElement builds an Element for a listItem
|
|
||||||
CreateListElement(*listItem) (apply.Element, error)
|
|
||||||
|
|
||||||
// CreateMapElement builds an Element for a mapItem
|
|
||||||
CreateMapElement(*mapItem) (apply.Element, error)
|
|
||||||
|
|
||||||
// CreateTypeElement builds an Element for a typeItem
|
|
||||||
CreateTypeElement(*typeItem) (apply.Element, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ElementBuildingVisitor creates an Elements from Items
|
|
||||||
// An Element combines the values from the Item with the field metadata.
|
|
||||||
type ElementBuildingVisitor struct {
|
|
||||||
resources openapi.Resources
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePrimitiveElement creates a primitiveElement
|
|
||||||
func (v ElementBuildingVisitor) CreatePrimitiveElement(item *primitiveItem) (apply.Element, error) {
|
|
||||||
return v.primitiveElement(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateListElement creates a ListElement
|
|
||||||
func (v ElementBuildingVisitor) CreateListElement(item *listItem) (apply.Element, error) {
|
|
||||||
meta, err := getFieldMeta(item.GetMeta(), item.Name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if meta.GetFieldMergeType() == apply.MergeStrategy {
|
|
||||||
return v.mergeListElement(meta, item)
|
|
||||||
}
|
|
||||||
return v.replaceListElement(meta, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMapElement creates a mapElement
|
|
||||||
func (v ElementBuildingVisitor) CreateMapElement(item *mapItem) (apply.Element, error) {
|
|
||||||
meta, err := getFieldMeta(item.GetMeta(), item.Name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return v.mapElement(meta, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTypeElement creates a typeElement
|
|
||||||
func (v ElementBuildingVisitor) CreateTypeElement(item *typeItem) (apply.Element, error) {
|
|
||||||
meta, err := getFieldMeta(item.GetMeta(), item.Name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return v.typeElement(meta, item)
|
|
||||||
}
|
|
|
@ -44,11 +44,9 @@ import (
|
||||||
"k8s.io/client-go/discovery"
|
"k8s.io/client-go/discovery"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
oapi "k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl"
|
"k8s.io/kubernetes/pkg/kubectl"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/delete"
|
"k8s.io/kubernetes/pkg/kubectl/cmd/delete"
|
||||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/util/templates"
|
"k8s.io/kubernetes/pkg/kubectl/util/templates"
|
||||||
|
@ -73,7 +71,6 @@ type ApplyOptions struct {
|
||||||
cmdBaseName string
|
cmdBaseName string
|
||||||
All bool
|
All bool
|
||||||
Overwrite bool
|
Overwrite bool
|
||||||
OpenApiPatch bool
|
|
||||||
PruneWhitelist []string
|
PruneWhitelist []string
|
||||||
ShouldIncludeUninitialized bool
|
ShouldIncludeUninitialized bool
|
||||||
|
|
||||||
|
@ -82,7 +79,6 @@ type ApplyOptions struct {
|
||||||
Mapper meta.RESTMapper
|
Mapper meta.RESTMapper
|
||||||
DynamicClient dynamic.Interface
|
DynamicClient dynamic.Interface
|
||||||
DiscoveryClient discovery.DiscoveryInterface
|
DiscoveryClient discovery.DiscoveryInterface
|
||||||
OpenAPISchema openapi.Resources
|
|
||||||
|
|
||||||
Namespace string
|
Namespace string
|
||||||
EnforceNamespace bool
|
EnforceNamespace bool
|
||||||
|
@ -133,7 +129,6 @@ func NewApplyOptions(ioStreams genericclioptions.IOStreams) *ApplyOptions {
|
||||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||||
|
|
||||||
Overwrite: true,
|
Overwrite: true,
|
||||||
OpenApiPatch: true,
|
|
||||||
|
|
||||||
Recorder: genericclioptions.NoopRecorder{},
|
Recorder: genericclioptions.NoopRecorder{},
|
||||||
|
|
||||||
|
@ -174,7 +169,6 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions
|
||||||
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
|
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
|
||||||
cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources in the namespace of the specified resource types.")
|
cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources in the namespace of the specified resource types.")
|
||||||
cmd.Flags().StringArrayVar(&o.PruneWhitelist, "prune-whitelist", o.PruneWhitelist, "Overwrite the default whitelist with <group/version/kind> for --prune")
|
cmd.Flags().StringArrayVar(&o.PruneWhitelist, "prune-whitelist", o.PruneWhitelist, "Overwrite the default whitelist with <group/version/kind> for --prune")
|
||||||
cmd.Flags().BoolVar(&o.OpenApiPatch, "openapi-patch", o.OpenApiPatch, "If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.")
|
|
||||||
cmd.Flags().BoolVar(&o.ServerDryRun, "server-dry-run", o.ServerDryRun, "If true, request will be sent to server with dry-run flag, which means the modifications won't be persisted. This is an alpha feature and flag.")
|
cmd.Flags().BoolVar(&o.ServerDryRun, "server-dry-run", o.ServerDryRun, "If true, request will be sent to server with dry-run flag, which means the modifications won't be persisted. This is an alpha feature and flag.")
|
||||||
cmdutil.AddDryRunFlag(cmd)
|
cmdutil.AddDryRunFlag(cmd)
|
||||||
cmdutil.AddIncludeUninitializedFlag(cmd)
|
cmdutil.AddIncludeUninitializedFlag(cmd)
|
||||||
|
@ -225,7 +219,6 @@ func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
|
||||||
o.DeleteOptions = o.DeleteFlags.ToOptions(dynamicClient, o.IOStreams)
|
o.DeleteOptions = o.DeleteFlags.ToOptions(dynamicClient, o.IOStreams)
|
||||||
o.ShouldIncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, o.Prune)
|
o.ShouldIncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, o.Prune)
|
||||||
|
|
||||||
o.OpenAPISchema, _ = f.OpenAPISchema()
|
|
||||||
o.Validator, err = f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
|
o.Validator, err = f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
|
||||||
o.Builder = f.NewBuilder()
|
o.Builder = f.NewBuilder()
|
||||||
o.Mapper, err = f.ToRESTMapper()
|
o.Mapper, err = f.ToRESTMapper()
|
||||||
|
@ -295,14 +288,8 @@ func parsePruneResources(mapper meta.RESTMapper, gvks []string) ([]pruneResource
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *ApplyOptions) Run() error {
|
func (o *ApplyOptions) Run() error {
|
||||||
var openapiSchema openapi.Resources
|
|
||||||
if o.OpenApiPatch {
|
|
||||||
openapiSchema = o.OpenAPISchema
|
|
||||||
}
|
|
||||||
|
|
||||||
dryRunVerifier := &DryRunVerifier{
|
dryRunVerifier := &DryRunVerifier{
|
||||||
Finder: cmdutil.NewCRDFinder(cmdutil.CRDFromDynamic(o.DynamicClient)),
|
Finder: cmdutil.NewCRDFinder(cmdutil.CRDFromDynamic(o.DynamicClient)),
|
||||||
OpenAPIGetter: o.DiscoveryClient,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// include the uninitialized objects by default if --prune is true
|
// include the uninitialized objects by default if --prune is true
|
||||||
|
@ -437,7 +424,6 @@ func (o *ApplyOptions) Run() error {
|
||||||
Timeout: o.DeleteOptions.Timeout,
|
Timeout: o.DeleteOptions.Timeout,
|
||||||
GracePeriod: o.DeleteOptions.GracePeriod,
|
GracePeriod: o.DeleteOptions.GracePeriod,
|
||||||
ServerDryRun: o.ServerDryRun,
|
ServerDryRun: o.ServerDryRun,
|
||||||
OpenapiSchema: openapiSchema,
|
|
||||||
Retries: maxPatchRetry,
|
Retries: maxPatchRetry,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -707,8 +693,6 @@ type Patcher struct {
|
||||||
|
|
||||||
// Number of retries to make if the patch fails with conflict
|
// Number of retries to make if the patch fails with conflict
|
||||||
Retries int
|
Retries int
|
||||||
|
|
||||||
OpenapiSchema openapi.Resources
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DryRunVerifier verifies if a given group-version-kind supports DryRun
|
// DryRunVerifier verifies if a given group-version-kind supports DryRun
|
||||||
|
@ -722,28 +706,15 @@ type Patcher struct {
|
||||||
// requires an extra round-trip to the server.
|
// requires an extra round-trip to the server.
|
||||||
type DryRunVerifier struct {
|
type DryRunVerifier struct {
|
||||||
Finder cmdutil.CRDFinder
|
Finder cmdutil.CRDFinder
|
||||||
OpenAPIGetter discovery.OpenAPISchemaInterface
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasSupport verifies if the given gvk supports DryRun. An error is
|
// HasSupport verifies if the given gvk supports DryRun. An error is
|
||||||
// returned if it doesn't.
|
// returned if it doesn't.
|
||||||
func (v *DryRunVerifier) HasSupport(gvk schema.GroupVersionKind) error {
|
func (v *DryRunVerifier) HasSupport(gvk schema.GroupVersionKind) error {
|
||||||
oapi, err := v.OpenAPIGetter.OpenAPISchema()
|
supports, err := v.Finder.HasCRD(gvk.GroupKind())
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to download openapi: %v", err)
|
|
||||||
}
|
|
||||||
supports, err := openapi.SupportsDryRun(oapi, gvk)
|
|
||||||
if err != nil {
|
|
||||||
// We assume that we couldn't find the type, then check for namespace:
|
|
||||||
supports, _ = openapi.SupportsDryRun(oapi, schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"})
|
|
||||||
// If namespace supports dryRun, then we will support dryRun for CRDs only.
|
|
||||||
if supports {
|
|
||||||
supports, err = v.Finder.HasCRD(gvk.GroupKind())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to check CRD: %v", err)
|
return fmt.Errorf("failed to check CRD: %v", err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
if !supports {
|
if !supports {
|
||||||
return fmt.Errorf("%v doesn't support dry-run", gvk)
|
return fmt.Errorf("%v doesn't support dry-run", gvk)
|
||||||
}
|
}
|
||||||
|
@ -782,7 +753,6 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, source, names
|
||||||
var patchType types.PatchType
|
var patchType types.PatchType
|
||||||
var patch []byte
|
var patch []byte
|
||||||
var lookupPatchMeta strategicpatch.LookupPatchMeta
|
var lookupPatchMeta strategicpatch.LookupPatchMeta
|
||||||
var schema oapi.Schema
|
|
||||||
createPatchErrFormat := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
|
createPatchErrFormat := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
|
||||||
|
|
||||||
// Create the versioned struct from the type defined in the restmapping
|
// Create the versioned struct from the type defined in the restmapping
|
||||||
|
@ -807,20 +777,6 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, source, names
|
||||||
// Compute a three way strategic merge patch to send to server.
|
// Compute a three way strategic merge patch to send to server.
|
||||||
patchType = types.StrategicMergePatchType
|
patchType = types.StrategicMergePatchType
|
||||||
|
|
||||||
// Try to use openapi first if the openapi spec is available and can successfully calculate the patch.
|
|
||||||
// Otherwise, fall back to baked-in types.
|
|
||||||
if p.OpenapiSchema != nil {
|
|
||||||
if schema = p.OpenapiSchema.LookupResource(p.Mapping.GroupVersionKind); schema != nil {
|
|
||||||
lookupPatchMeta = strategicpatch.PatchMetaFromOpenAPI{Schema: schema}
|
|
||||||
if openapiPatch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.Overwrite); err != nil {
|
|
||||||
fmt.Fprintf(errOut, "warning: error calculating patch from openapi spec: %v\n", err)
|
|
||||||
} else {
|
|
||||||
patchType = types.StrategicMergePatchType
|
|
||||||
patch = openapiPatch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if patch == nil {
|
if patch == nil {
|
||||||
lookupPatchMeta, err = strategicpatch.NewPatchMetaFromStruct(versionedObject)
|
lookupPatchMeta, err = strategicpatch.NewPatchMetaFromStruct(versionedObject)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -45,11 +45,9 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/create"
|
"k8s.io/kubernetes/pkg/kubectl/cmd/create"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/delete"
|
"k8s.io/kubernetes/pkg/kubectl/cmd/delete"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/describe"
|
"k8s.io/kubernetes/pkg/kubectl/cmd/describe"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/diff"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/drain"
|
"k8s.io/kubernetes/pkg/kubectl/cmd/drain"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/edit"
|
"k8s.io/kubernetes/pkg/kubectl/cmd/edit"
|
||||||
cmdexec "k8s.io/kubernetes/pkg/kubectl/cmd/exec"
|
cmdexec "k8s.io/kubernetes/pkg/kubectl/cmd/exec"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/explain"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/expose"
|
"k8s.io/kubernetes/pkg/kubectl/cmd/expose"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/get"
|
"k8s.io/kubernetes/pkg/kubectl/cmd/get"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/label"
|
"k8s.io/kubernetes/pkg/kubectl/cmd/label"
|
||||||
|
@ -457,7 +455,6 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
|
||||||
{
|
{
|
||||||
Message: "Basic Commands (Intermediate):",
|
Message: "Basic Commands (Intermediate):",
|
||||||
Commands: []*cobra.Command{
|
Commands: []*cobra.Command{
|
||||||
explain.NewCmdExplain("kubectl", f, ioStreams),
|
|
||||||
get.NewCmdGet("kubectl", f, ioStreams),
|
get.NewCmdGet("kubectl", f, ioStreams),
|
||||||
edit.NewCmdEdit(f, ioStreams),
|
edit.NewCmdEdit(f, ioStreams),
|
||||||
delete.NewCmdDelete(f, ioStreams),
|
delete.NewCmdDelete(f, ioStreams),
|
||||||
|
@ -500,7 +497,6 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
|
||||||
{
|
{
|
||||||
Message: "Advanced Commands:",
|
Message: "Advanced Commands:",
|
||||||
Commands: []*cobra.Command{
|
Commands: []*cobra.Command{
|
||||||
diff.NewCmdDiff(f, ioStreams),
|
|
||||||
apply.NewCmdApply("kubectl", f, ioStreams),
|
apply.NewCmdApply("kubectl", f, ioStreams),
|
||||||
patch.NewCmdPatch(f, ioStreams),
|
patch.NewCmdPatch(f, ioStreams),
|
||||||
replace.NewCmdReplace(f, ioStreams),
|
replace.NewCmdReplace(f, ioStreams),
|
||||||
|
|
|
@ -1,446 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 diff
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/jonboulle/clockwork"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"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/cli-runtime/pkg/genericclioptions"
|
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
|
|
||||||
"k8s.io/klog"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/apply"
|
|
||||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/util/templates"
|
|
||||||
"k8s.io/utils/exec"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
diffLong = templates.LongDesc(i18n.T(`
|
|
||||||
Diff configurations specified by filename or stdin between the current online
|
|
||||||
configuration, and the configuration as it would be if applied.
|
|
||||||
|
|
||||||
Output is always YAML.
|
|
||||||
|
|
||||||
KUBECTL_EXTERNAL_DIFF environment variable can be used to select your own
|
|
||||||
diff command. By default, the "diff" command available in your path will be
|
|
||||||
run with "-u" (unicode) and "-N" (treat new files as empty) options.`))
|
|
||||||
diffExample = templates.Examples(i18n.T(`
|
|
||||||
# Diff resources included in pod.json.
|
|
||||||
kubectl diff -f pod.json
|
|
||||||
|
|
||||||
# Diff file read from stdin
|
|
||||||
cat service.yaml | kubectl diff -f -`))
|
|
||||||
)
|
|
||||||
|
|
||||||
// Number of times we try to diff before giving-up
|
|
||||||
const maxRetries = 4
|
|
||||||
|
|
||||||
type DiffOptions struct {
|
|
||||||
FilenameOptions resource.FilenameOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkDiffArgs(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) != 0 {
|
|
||||||
return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCmdDiff(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
|
|
||||||
var options DiffOptions
|
|
||||||
diff := DiffProgram{
|
|
||||||
Exec: exec.New(),
|
|
||||||
IOStreams: streams,
|
|
||||||
}
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "diff -f FILENAME",
|
|
||||||
DisableFlagsInUseLine: true,
|
|
||||||
Short: i18n.T("Diff live version against would-be applied version"),
|
|
||||||
Long: diffLong,
|
|
||||||
Example: diffExample,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
cmdutil.CheckErr(checkDiffArgs(cmd, args))
|
|
||||||
cmdutil.CheckErr(RunDiff(f, &diff, &options))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
usage := "contains the configuration to diff"
|
|
||||||
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
|
|
||||||
cmd.MarkFlagRequired("filename")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiffProgram finds and run the diff program. The value of
|
|
||||||
// KUBECTL_EXTERNAL_DIFF environment variable will be used a diff
|
|
||||||
// program. By default, `diff(1)` will be used.
|
|
||||||
type DiffProgram struct {
|
|
||||||
Exec exec.Interface
|
|
||||||
genericclioptions.IOStreams
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DiffProgram) getCommand(args ...string) exec.Cmd {
|
|
||||||
diff := ""
|
|
||||||
if envDiff := os.Getenv("KUBECTL_EXTERNAL_DIFF"); envDiff != "" {
|
|
||||||
diff = envDiff
|
|
||||||
} else {
|
|
||||||
diff = "diff"
|
|
||||||
args = append([]string{"-u", "-N"}, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := d.Exec.Command(diff, args...)
|
|
||||||
cmd.SetStdout(d.Out)
|
|
||||||
cmd.SetStderr(d.ErrOut)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run runs the detected diff program. `from` and `to` are the directory to diff.
|
|
||||||
func (d *DiffProgram) Run(from, to string) error {
|
|
||||||
return d.getCommand(from, to).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printer is used to print an object.
|
|
||||||
type Printer struct{}
|
|
||||||
|
|
||||||
// Print the object inside the writer w.
|
|
||||||
func (p *Printer) Print(obj runtime.Object, w io.Writer) error {
|
|
||||||
if obj == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
data, err := yaml.Marshal(obj)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write(data)
|
|
||||||
return err
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiffVersion gets the proper version of objects, and aggregate them into a directory.
|
|
||||||
type DiffVersion struct {
|
|
||||||
Dir *Directory
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDiffVersion creates a new DiffVersion with the named version.
|
|
||||||
func NewDiffVersion(name string) (*DiffVersion, error) {
|
|
||||||
dir, err := CreateDirectory(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &DiffVersion{
|
|
||||||
Dir: dir,
|
|
||||||
Name: name,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *DiffVersion) getObject(obj Object) (runtime.Object, error) {
|
|
||||||
switch v.Name {
|
|
||||||
case "LIVE":
|
|
||||||
return obj.Live(), nil
|
|
||||||
case "MERGED":
|
|
||||||
return obj.Merged()
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("Unknown version: %v", v.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print prints the object using the printer into a new file in the directory.
|
|
||||||
func (v *DiffVersion) Print(obj Object, printer Printer) error {
|
|
||||||
vobj, err := v.getObject(obj)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f, err := v.Dir.NewFile(obj.Name())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
return printer.Print(vobj, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Directory creates a new temp directory, and allows to easily create new files.
|
|
||||||
type Directory struct {
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDirectory does create the actual disk directory, and return a
|
|
||||||
// new representation of it.
|
|
||||||
func CreateDirectory(prefix string) (*Directory, error) {
|
|
||||||
name, err := ioutil.TempDir("", prefix+"-")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Directory{
|
|
||||||
Name: name,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFile creates a new file in the directory.
|
|
||||||
func (d *Directory) NewFile(name string) (*os.File, error) {
|
|
||||||
return os.OpenFile(filepath.Join(d.Name, name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes the directory recursively.
|
|
||||||
func (d *Directory) Delete() error {
|
|
||||||
return os.RemoveAll(d.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Object is an interface that let's you retrieve multiple version of
|
|
||||||
// it.
|
|
||||||
type Object interface {
|
|
||||||
Live() runtime.Object
|
|
||||||
Merged() (runtime.Object, error)
|
|
||||||
|
|
||||||
Name() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfoObject is an implementation of the Object interface. It gets all
|
|
||||||
// the information from the Info object.
|
|
||||||
type InfoObject struct {
|
|
||||||
LocalObj runtime.Object
|
|
||||||
Info *resource.Info
|
|
||||||
Encoder runtime.Encoder
|
|
||||||
OpenAPI openapi.Resources
|
|
||||||
Force bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Object = &InfoObject{}
|
|
||||||
|
|
||||||
// Returns the live version of the object
|
|
||||||
func (obj InfoObject) Live() runtime.Object {
|
|
||||||
return obj.Info.Object
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the "merged" object, as it would look like if applied or
|
|
||||||
// created.
|
|
||||||
func (obj InfoObject) Merged() (runtime.Object, error) {
|
|
||||||
// Build the patcher, and then apply the patch with dry-run, unless the object doesn't exist, in which case we need to create it.
|
|
||||||
if obj.Live() == nil {
|
|
||||||
// Dry-run create if the object doesn't exist.
|
|
||||||
return resource.NewHelper(obj.Info.Client, obj.Info.Mapping).Create(
|
|
||||||
obj.Info.Namespace,
|
|
||||||
true,
|
|
||||||
obj.LocalObj,
|
|
||||||
&metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var resourceVersion *string
|
|
||||||
if !obj.Force {
|
|
||||||
accessor, err := meta.Accessor(obj.Info.Object)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
str := accessor.GetResourceVersion()
|
|
||||||
resourceVersion = &str
|
|
||||||
}
|
|
||||||
|
|
||||||
modified, err := kubectl.GetModifiedConfiguration(obj.LocalObj, false, unstructured.UnstructuredJSONScheme)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is using the patcher from apply, to keep the same behavior.
|
|
||||||
// We plan on replacing this with server-side apply when it becomes available.
|
|
||||||
patcher := &apply.Patcher{
|
|
||||||
Mapping: obj.Info.Mapping,
|
|
||||||
Helper: resource.NewHelper(obj.Info.Client, obj.Info.Mapping),
|
|
||||||
Overwrite: true,
|
|
||||||
BackOff: clockwork.NewRealClock(),
|
|
||||||
ServerDryRun: true,
|
|
||||||
OpenapiSchema: obj.OpenAPI,
|
|
||||||
ResourceVersion: resourceVersion,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, result, err := patcher.Patch(obj.Info.Object, modified, obj.Info.Source, obj.Info.Namespace, obj.Info.Name, nil)
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (obj InfoObject) Name() string {
|
|
||||||
group := ""
|
|
||||||
if obj.Info.Mapping.GroupVersionKind.Group != "" {
|
|
||||||
group = fmt.Sprintf("%v.", obj.Info.Mapping.GroupVersionKind.Group)
|
|
||||||
}
|
|
||||||
return group + fmt.Sprintf(
|
|
||||||
"%v.%v.%v.%v",
|
|
||||||
obj.Info.Mapping.GroupVersionKind.Version,
|
|
||||||
obj.Info.Mapping.GroupVersionKind.Kind,
|
|
||||||
obj.Info.Namespace,
|
|
||||||
obj.Info.Name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Differ creates two DiffVersion and diffs them.
|
|
||||||
type Differ struct {
|
|
||||||
From *DiffVersion
|
|
||||||
To *DiffVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDiffer(from, to string) (*Differ, error) {
|
|
||||||
differ := Differ{}
|
|
||||||
var err error
|
|
||||||
differ.From, err = NewDiffVersion(from)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
differ.To, err = NewDiffVersion(to)
|
|
||||||
if err != nil {
|
|
||||||
differ.From.Dir.Delete()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &differ, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diff diffs to versions of a specific object, and print both versions to directories.
|
|
||||||
func (d *Differ) Diff(obj Object, printer Printer) error {
|
|
||||||
if err := d.From.Print(obj, printer); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := d.To.Print(obj, printer); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run runs the diff program against both directories.
|
|
||||||
func (d *Differ) Run(diff *DiffProgram) error {
|
|
||||||
return diff.Run(d.From.Dir.Name, d.To.Dir.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TearDown removes both temporary directories recursively.
|
|
||||||
func (d *Differ) TearDown() {
|
|
||||||
d.From.Dir.Delete() // Ignore error
|
|
||||||
d.To.Dir.Delete() // Ignore error
|
|
||||||
}
|
|
||||||
|
|
||||||
func isConflict(err error) bool {
|
|
||||||
return err != nil && errors.IsConflict(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunDiff uses the factory to parse file arguments, find the version to
|
|
||||||
// diff, and find each Info object for each files, and runs against the
|
|
||||||
// differ.
|
|
||||||
func RunDiff(f cmdutil.Factory, diff *DiffProgram, options *DiffOptions) error {
|
|
||||||
schema, err := f.OpenAPISchema()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
discovery, err := f.ToDiscoveryClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamic, err := f.DynamicClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dryRunVerifier := &apply.DryRunVerifier{
|
|
||||||
Finder: cmdutil.NewCRDFinder(cmdutil.CRDFromDynamic(dynamic)),
|
|
||||||
OpenAPIGetter: discovery,
|
|
||||||
}
|
|
||||||
|
|
||||||
differ, err := NewDiffer("LIVE", "MERGED")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer differ.TearDown()
|
|
||||||
|
|
||||||
printer := Printer{}
|
|
||||||
|
|
||||||
cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r := f.NewBuilder().
|
|
||||||
Unstructured().
|
|
||||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
|
||||||
FilenameParam(enforceNamespace, &options.FilenameOptions).
|
|
||||||
Flatten().
|
|
||||||
Do()
|
|
||||||
if err := r.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = r.Visit(func(info *resource.Info, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := dryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
local := info.Object.DeepCopyObject()
|
|
||||||
for i := 1; i <= maxRetries; i++ {
|
|
||||||
if err = info.Get(); err != nil {
|
|
||||||
if !errors.IsNotFound(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info.Object = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
force := i == maxRetries
|
|
||||||
if force {
|
|
||||||
klog.Warningf(
|
|
||||||
"Object (%v: %v) keeps changing, diffing without lock",
|
|
||||||
info.Object.GetObjectKind().GroupVersionKind(),
|
|
||||||
info.Name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
obj := InfoObject{
|
|
||||||
LocalObj: local,
|
|
||||||
Info: info,
|
|
||||||
Encoder: scheme.DefaultJSONEncoder(),
|
|
||||||
OpenAPI: schema,
|
|
||||||
Force: force,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = differ.Diff(obj, printer)
|
|
||||||
if !isConflict(err) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return differ.Run(diff)
|
|
||||||
}
|
|
|
@ -1,196 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 diff
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
|
||||||
"k8s.io/utils/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FakeObject struct {
|
|
||||||
name string
|
|
||||||
merged map[string]interface{}
|
|
||||||
live map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Object = &FakeObject{}
|
|
||||||
|
|
||||||
func (f *FakeObject) Name() string {
|
|
||||||
return f.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeObject) Merged() (runtime.Object, error) {
|
|
||||||
return &unstructured.Unstructured{Object: f.merged}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeObject) Live() runtime.Object {
|
|
||||||
return &unstructured.Unstructured{Object: f.live}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDiffProgram(t *testing.T) {
|
|
||||||
os.Setenv("KUBECTL_EXTERNAL_DIFF", "echo")
|
|
||||||
streams, _, stdout, _ := genericclioptions.NewTestIOStreams()
|
|
||||||
diff := DiffProgram{
|
|
||||||
IOStreams: streams,
|
|
||||||
Exec: exec.New(),
|
|
||||||
}
|
|
||||||
err := diff.Run("one", "two")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if output := stdout.String(); output != "one two\n" {
|
|
||||||
t.Fatalf(`stdout = %q, expected "one two\n"`, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrinter(t *testing.T) {
|
|
||||||
printer := Printer{}
|
|
||||||
|
|
||||||
obj := &unstructured.Unstructured{Object: map[string]interface{}{
|
|
||||||
"string": "string",
|
|
||||||
"list": []int{1, 2, 3},
|
|
||||||
"int": 12,
|
|
||||||
}}
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
printer.Print(obj, &buf)
|
|
||||||
want := `int: 12
|
|
||||||
list:
|
|
||||||
- 1
|
|
||||||
- 2
|
|
||||||
- 3
|
|
||||||
string: string
|
|
||||||
`
|
|
||||||
if buf.String() != want {
|
|
||||||
t.Errorf("Print() = %q, want %q", buf.String(), want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDiffVersion(t *testing.T) {
|
|
||||||
diff, err := NewDiffVersion("MERGED")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer diff.Dir.Delete()
|
|
||||||
|
|
||||||
obj := FakeObject{
|
|
||||||
name: "bla",
|
|
||||||
live: map[string]interface{}{"live": true},
|
|
||||||
merged: map[string]interface{}{"merged": true},
|
|
||||||
}
|
|
||||||
err = diff.Print(&obj, Printer{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fcontent, err := ioutil.ReadFile(path.Join(diff.Dir.Name, obj.Name()))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
econtent := "merged: true\n"
|
|
||||||
if string(fcontent) != econtent {
|
|
||||||
t.Fatalf("File has %q, expected %q", string(fcontent), econtent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDirectory(t *testing.T) {
|
|
||||||
dir, err := CreateDirectory("prefix")
|
|
||||||
defer dir.Delete()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = os.Stat(dir.Name)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(filepath.Base(dir.Name), "prefix") {
|
|
||||||
t.Fatalf(`Directory doesn't start with "prefix": %q`, dir.Name)
|
|
||||||
}
|
|
||||||
entries, err := ioutil.ReadDir(dir.Name)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(entries) != 0 {
|
|
||||||
t.Fatalf("Directory should be empty, has %d elements", len(entries))
|
|
||||||
}
|
|
||||||
_, err = dir.NewFile("ONE")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = dir.NewFile("TWO")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
entries, err = ioutil.ReadDir(dir.Name)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(entries) != 2 {
|
|
||||||
t.Fatalf("ReadDir should have two elements, has %d elements", len(entries))
|
|
||||||
}
|
|
||||||
err = dir.Delete()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = os.Stat(dir.Name)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("Directory should be gone, still present.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDiffer(t *testing.T) {
|
|
||||||
diff, err := NewDiffer("LIVE", "MERGED")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer diff.TearDown()
|
|
||||||
|
|
||||||
obj := FakeObject{
|
|
||||||
name: "bla",
|
|
||||||
live: map[string]interface{}{"live": true},
|
|
||||||
merged: map[string]interface{}{"merged": true},
|
|
||||||
}
|
|
||||||
err = diff.Diff(&obj, Printer{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fcontent, err := ioutil.ReadFile(path.Join(diff.From.Dir.Name, obj.Name()))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
econtent := "live: true\n"
|
|
||||||
if string(fcontent) != econtent {
|
|
||||||
t.Fatalf("File has %q, expected %q", string(fcontent), econtent)
|
|
||||||
}
|
|
||||||
|
|
||||||
fcontent, err = ioutil.ReadFile(path.Join(diff.To.Dir.Name, obj.Name()))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
econtent = "merged: true\n"
|
|
||||||
if string(fcontent) != econtent {
|
|
||||||
t.Fatalf("File has %q, expected %q", string(fcontent), econtent)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,158 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2014 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 explain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
|
||||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/explain"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/util/templates"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
explainLong = templates.LongDesc(`
|
|
||||||
List the fields for supported resources
|
|
||||||
|
|
||||||
This command describes the fields associated with each supported API resource.
|
|
||||||
Fields are identified via a simple JSONPath identifier:
|
|
||||||
|
|
||||||
<type>.<fieldName>[.<fieldName>]
|
|
||||||
|
|
||||||
Add the --recursive flag to display all of the fields at once without descriptions.
|
|
||||||
Information about each field is retrieved from the server in OpenAPI format.`)
|
|
||||||
|
|
||||||
explainExamples = templates.Examples(i18n.T(`
|
|
||||||
# Get the documentation of the resource and its fields
|
|
||||||
kubectl explain pods
|
|
||||||
|
|
||||||
# Get the documentation of a specific field of a resource
|
|
||||||
kubectl explain pods.spec.containers`))
|
|
||||||
)
|
|
||||||
|
|
||||||
type ExplainOptions struct {
|
|
||||||
genericclioptions.IOStreams
|
|
||||||
|
|
||||||
CmdParent string
|
|
||||||
ApiVersion string
|
|
||||||
Recursive bool
|
|
||||||
|
|
||||||
Mapper meta.RESTMapper
|
|
||||||
Schema openapi.Resources
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewExplainOptions(parent string, streams genericclioptions.IOStreams) *ExplainOptions {
|
|
||||||
return &ExplainOptions{
|
|
||||||
IOStreams: streams,
|
|
||||||
CmdParent: parent,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCmdExplain returns a cobra command for swagger docs
|
|
||||||
func NewCmdExplain(parent string, f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
|
|
||||||
o := NewExplainOptions(parent, streams)
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "explain RESOURCE",
|
|
||||||
DisableFlagsInUseLine: true,
|
|
||||||
Short: i18n.T("Documentation of resources"),
|
|
||||||
Long: explainLong + "\n\n" + cmdutil.SuggestApiResources(parent),
|
|
||||||
Example: explainExamples,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
cmdutil.CheckErr(o.Complete(f, cmd))
|
|
||||||
cmdutil.CheckErr(o.Validate(args))
|
|
||||||
cmdutil.CheckErr(o.Run(args))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.Flags().BoolVar(&o.Recursive, "recursive", o.Recursive, "Print the fields of fields (Currently only 1 level deep)")
|
|
||||||
cmd.Flags().StringVar(&o.ApiVersion, "api-version", o.ApiVersion, "Get different explanations for particular API version")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ExplainOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
|
|
||||||
var err error
|
|
||||||
o.Mapper, err = f.ToRESTMapper()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
o.Schema, err = f.OpenAPISchema()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ExplainOptions) Validate(args []string) error {
|
|
||||||
if len(args) == 0 {
|
|
||||||
return fmt.Errorf("You must specify the type of resource to explain. %s\n", cmdutil.SuggestApiResources(o.CmdParent))
|
|
||||||
}
|
|
||||||
if len(args) > 1 {
|
|
||||||
return fmt.Errorf("We accept only this format: explain RESOURCE\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run executes the appropriate steps to print a model's documentation
|
|
||||||
func (o *ExplainOptions) Run(args []string) error {
|
|
||||||
recursive := o.Recursive
|
|
||||||
apiVersionString := o.ApiVersion
|
|
||||||
|
|
||||||
// TODO: After we figured out the new syntax to separate group and resource, allow
|
|
||||||
// the users to use it in explain (kubectl explain <group><syntax><resource>).
|
|
||||||
// Refer to issue #16039 for why we do this. Refer to PR #15808 that used "/" syntax.
|
|
||||||
inModel, fieldsPath, err := explain.SplitAndParseResourceRequest(args[0], o.Mapper)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: We should deduce the group for a resource by discovering the supported resources at server.
|
|
||||||
fullySpecifiedGVR, groupResource := schema.ParseResourceArg(inModel)
|
|
||||||
gvk := schema.GroupVersionKind{}
|
|
||||||
if fullySpecifiedGVR != nil {
|
|
||||||
gvk, _ = o.Mapper.KindFor(*fullySpecifiedGVR)
|
|
||||||
}
|
|
||||||
if gvk.Empty() {
|
|
||||||
gvk, err = o.Mapper.KindFor(groupResource.WithVersion(""))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(apiVersionString) != 0 {
|
|
||||||
apiVersion, err := schema.ParseGroupVersion(apiVersionString)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gvk = apiVersion.WithKind(gvk.Kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
schema := o.Schema.LookupResource(gvk)
|
|
||||||
if schema == nil {
|
|
||||||
return fmt.Errorf("Couldn't find resource for %q", gvk)
|
|
||||||
}
|
|
||||||
|
|
||||||
return explain.PrintModelDescription(fieldsPath, o.Out, schema, gvk, recursive)
|
|
||||||
}
|
|
|
@ -55,7 +55,6 @@ type GetOptions struct {
|
||||||
PrintFlags *PrintFlags
|
PrintFlags *PrintFlags
|
||||||
ToPrinter func(*meta.RESTMapping, bool, bool) (printers.ResourcePrinterFunc, error)
|
ToPrinter func(*meta.RESTMapping, bool, bool) (printers.ResourcePrinterFunc, error)
|
||||||
IsHumanReadablePrinter bool
|
IsHumanReadablePrinter bool
|
||||||
PrintWithOpenAPICols bool
|
|
||||||
|
|
||||||
CmdParent string
|
CmdParent string
|
||||||
|
|
||||||
|
@ -231,11 +230,6 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
|
||||||
printFlags := o.PrintFlags.Copy()
|
printFlags := o.PrintFlags.Copy()
|
||||||
|
|
||||||
if mapping != nil {
|
if mapping != nil {
|
||||||
if !cmdSpecifiesOutputFmt(cmd) && o.PrintWithOpenAPICols {
|
|
||||||
if apiSchema, err := f.OpenAPISchema(); err == nil {
|
|
||||||
printFlags.UseOpenAPIColumns(apiSchema, mapping)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printFlags.SetKind(mapping.GroupVersionKind.GroupKind())
|
printFlags.SetKind(mapping.GroupVersionKind.GroupKind())
|
||||||
}
|
}
|
||||||
if withNamespace {
|
if withNamespace {
|
||||||
|
@ -401,11 +395,6 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
|
||||||
return o.watch(f, cmd, args)
|
return o.watch(f, cmd, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// openapi printing is mutually exclusive with server side printing
|
|
||||||
if o.PrintWithOpenAPICols && o.ServerPrint {
|
|
||||||
fmt.Fprintf(o.IOStreams.ErrOut, "warning: --%s requested, --%s will be ignored\n", useOpenAPIPrintColumnFlagLabel, useServerPrintColumns)
|
|
||||||
}
|
|
||||||
|
|
||||||
chunkSize := o.ChunkSize
|
chunkSize := o.ChunkSize
|
||||||
if o.Sort {
|
if o.Sort {
|
||||||
// TODO(juanvallejo): in the future, we could have the client use chunking
|
// TODO(juanvallejo): in the future, we could have the client use chunking
|
||||||
|
@ -428,9 +417,6 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
|
||||||
Flatten().
|
Flatten().
|
||||||
TransformRequests(func(req *rest.Request) {
|
TransformRequests(func(req *rest.Request) {
|
||||||
// We need full objects if printing with openapi columns
|
// We need full objects if printing with openapi columns
|
||||||
if o.PrintWithOpenAPICols {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !o.ServerPrint || !o.IsHumanReadablePrinter {
|
if !o.ServerPrint || !o.IsHumanReadablePrinter {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -550,13 +536,6 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
|
||||||
lastMapping = mapping
|
lastMapping = mapping
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure a versioned object is passed to the custom-columns printer
|
|
||||||
// if we are using OpenAPI columns to print
|
|
||||||
if o.PrintWithOpenAPICols {
|
|
||||||
printer.PrintObj(info.Object, w)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
internalObj, err := legacyscheme.Scheme.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion())
|
internalObj, err := legacyscheme.Scheme.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if there's an error, try to print what you have (mirrors old behavior).
|
// if there's an error, try to print what you have (mirrors old behavior).
|
||||||
|
@ -839,7 +818,6 @@ func (o *GetOptions) printGeneric(r *resource.Result) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func addOpenAPIPrintColumnFlags(cmd *cobra.Command, opt *GetOptions) {
|
func addOpenAPIPrintColumnFlags(cmd *cobra.Command, opt *GetOptions) {
|
||||||
cmd.Flags().BoolVar(&opt.PrintWithOpenAPICols, useOpenAPIPrintColumnFlagLabel, opt.PrintWithOpenAPICols, "If true, use x-kubernetes-print-column metadata (if present) from the OpenAPI schema for displaying a resource.")
|
|
||||||
cmd.Flags().MarkDeprecated(useOpenAPIPrintColumnFlagLabel, "deprecated in favor of server-side printing")
|
cmd.Flags().MarkDeprecated(useOpenAPIPrintColumnFlagLabel, "deprecated in favor of server-side printing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -851,10 +829,6 @@ func shouldGetNewPrinterForMapping(printer printers.ResourcePrinter, lastMapping
|
||||||
return printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource
|
return printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdSpecifiesOutputFmt(cmd *cobra.Command) bool {
|
|
||||||
return cmdutil.GetFlagString(cmd, "output") != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func maybeWrapSortingPrinter(printer printers.ResourcePrinter, sortBy string) printers.ResourcePrinter {
|
func maybeWrapSortingPrinter(printer printers.ResourcePrinter, sortBy string) printers.ResourcePrinter {
|
||||||
if len(sortBy) != 0 {
|
if len(sortBy) != 0 {
|
||||||
return &SortingPrinter{
|
return &SortingPrinter{
|
||||||
|
|
|
@ -17,15 +17,11 @@ limitations under the License.
|
||||||
package get
|
package get
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
|
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PrintFlags composes common printer flag structs
|
// PrintFlags composes common printer flag structs
|
||||||
|
@ -73,34 +69,6 @@ func (f *PrintFlags) AllowedFormats() []string {
|
||||||
return formats
|
return formats
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseOpenAPIColumns modifies the output format, as well as the
|
|
||||||
// "allowMissingKeys" option for template printers, to values
|
|
||||||
// defined in the OpenAPI schema of a resource.
|
|
||||||
func (f *PrintFlags) UseOpenAPIColumns(api openapi.Resources, mapping *meta.RESTMapping) error {
|
|
||||||
// Found openapi metadata for this resource
|
|
||||||
schema := api.LookupResource(mapping.GroupVersionKind)
|
|
||||||
if schema == nil {
|
|
||||||
// Schema not found, return empty columns
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
columns, found := openapi.GetPrintColumns(schema.GetExtensions())
|
|
||||||
if !found {
|
|
||||||
// Extension not found, return empty columns
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.SplitN(columns, "=", 2)
|
|
||||||
if len(parts) < 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
allowMissingKeys := true
|
|
||||||
f.OutputFormat = &parts[0]
|
|
||||||
f.TemplateFlags.TemplateArgument = &parts[1]
|
|
||||||
f.TemplateFlags.AllowMissingKeys = &allowMissingKeys
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToPrinter attempts to find a composed set of PrintFlags suitable for
|
// ToPrinter attempts to find a composed set of PrintFlags suitable for
|
||||||
// returning a printer based on current flag values.
|
// returning a printer based on current flag values.
|
||||||
|
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/validation"
|
"k8s.io/kubernetes/pkg/kubectl/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -61,6 +60,4 @@ type Factory interface {
|
||||||
|
|
||||||
// Returns a schema that can validate objects stored on disk.
|
// Returns a schema that can validate objects stored on disk.
|
||||||
Validator(validate bool) (validation.Schema, error)
|
Validator(validate bool) (validation.Schema, error)
|
||||||
// OpenAPISchema returns the schema openapi schema definition
|
|
||||||
OpenAPISchema() (openapi.Resources, error)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,6 @@ limitations under the License.
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
|
@ -30,21 +28,11 @@ import (
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
|
||||||
openapivalidation "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/validation"
|
"k8s.io/kubernetes/pkg/kubectl/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
type factoryImpl struct {
|
type factoryImpl struct {
|
||||||
clientGetter genericclioptions.RESTClientGetter
|
clientGetter genericclioptions.RESTClientGetter
|
||||||
|
|
||||||
// openAPIGetter loads and caches openapi specs
|
|
||||||
openAPIGetter openAPIGetter
|
|
||||||
}
|
|
||||||
|
|
||||||
type openAPIGetter struct {
|
|
||||||
once sync.Once
|
|
||||||
getter openapi.Getter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFactory(clientGetter genericclioptions.RESTClientGetter) Factory {
|
func NewFactory(clientGetter genericclioptions.RESTClientGetter) Factory {
|
||||||
|
@ -148,30 +136,7 @@ func (f *factoryImpl) Validator(validate bool) (validation.Schema, error) {
|
||||||
return validation.NullSchema{}, nil
|
return validation.NullSchema{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resources, err := f.OpenAPISchema()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return validation.ConjunctiveSchema{
|
return validation.ConjunctiveSchema{
|
||||||
openapivalidation.NewSchemaValidation(resources),
|
|
||||||
validation.NoDoubleKeySchema{},
|
validation.NoDoubleKeySchema{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenAPISchema returns metadata and structural information about Kubernetes object definitions.
|
|
||||||
func (f *factoryImpl) OpenAPISchema() (openapi.Resources, error) {
|
|
||||||
discovery, err := f.clientGetter.ToDiscoveryClient()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lazily initialize the OpenAPIGetter once
|
|
||||||
f.openAPIGetter.once.Do(func() {
|
|
||||||
// Create the caching OpenAPIGetter
|
|
||||||
f.openAPIGetter.getter = openapi.NewOpenAPIGetter(discovery)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Delegate to the OpenAPIGetter
|
|
||||||
return f.openAPIGetter.getter.Get()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
"go_test",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = [
|
|
||||||
"doc.go",
|
|
||||||
"dryrun.go",
|
|
||||||
"extensions.go",
|
|
||||||
"openapi.go",
|
|
||||||
"openapi_getter.go",
|
|
||||||
],
|
|
||||||
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi",
|
|
||||||
deps = [
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
|
||||||
"//staging/src/k8s.io/client-go/discovery:go_default_library",
|
|
||||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
|
||||||
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
|
||||||
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
size = "small",
|
|
||||||
srcs = [
|
|
||||||
"dryrun_test.go",
|
|
||||||
"openapi_getter_test.go",
|
|
||||||
"openapi_suite_test.go",
|
|
||||||
"openapi_test.go",
|
|
||||||
],
|
|
||||||
data = ["//api/openapi-spec:swagger-spec"],
|
|
||||||
embed = [":go_default_library"],
|
|
||||||
deps = [
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
|
||||||
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
|
||||||
"//vendor/github.com/onsi/ginkgo:go_default_library",
|
|
||||||
"//vendor/github.com/onsi/ginkgo/config:go_default_library",
|
|
||||||
"//vendor/github.com/onsi/ginkgo/types:go_default_library",
|
|
||||||
"//vendor/github.com/onsi/gomega:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/util/proto/testing:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [
|
|
||||||
":package-srcs",
|
|
||||||
"//pkg/kubectl/cmd/util/openapi/testing:all-srcs",
|
|
||||||
"//pkg/kubectl/cmd/util/openapi/validation:all-srcs",
|
|
||||||
],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "testdata",
|
|
||||||
srcs = glob(["testdata/*"]),
|
|
||||||
)
|
|
|
@ -1,4 +0,0 @@
|
||||||
approvers:
|
|
||||||
- apelisse
|
|
||||||
reviewers:
|
|
||||||
- apelisse
|
|
|
@ -1,21 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 openapi is a collection of libraries for fetching the openapi spec
|
|
||||||
// from a Kubernetes server and then indexing the type definitions.
|
|
||||||
// The openapi spec contains the object model definitions and extensions metadata
|
|
||||||
// such as the patchStrategy and patchMergeKey for creating patches.
|
|
||||||
package openapi // k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi
|
|
|
@ -1,65 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
func hasGVKExtension(extensions []*openapi_v2.NamedAny, gvk schema.GroupVersionKind) bool {
|
|
||||||
for _, extension := range extensions {
|
|
||||||
if extension.GetValue().GetYaml() == "" ||
|
|
||||||
extension.GetName() != "x-kubernetes-group-version-kind" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var value map[string]string
|
|
||||||
err := yaml.Unmarshal([]byte(extension.GetValue().GetYaml()), &value)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if value["group"] == gvk.Group && value["kind"] == gvk.Kind && value["version"] == gvk.Version {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// SupportsDryRun is a method that let's us look in the OpenAPI if the
|
|
||||||
// specific group-version-kind supports the dryRun query parameter for
|
|
||||||
// the PATCH end-point.
|
|
||||||
func SupportsDryRun(doc *openapi_v2.Document, gvk schema.GroupVersionKind) (bool, error) {
|
|
||||||
for _, path := range doc.GetPaths().GetPath() {
|
|
||||||
// Is this describing the gvk we're looking for?
|
|
||||||
if !hasGVKExtension(path.GetValue().GetPatch().GetVendorExtension(), gvk) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, param := range path.GetValue().GetPatch().GetParameters() {
|
|
||||||
if param.GetParameter().GetNonBodyParameter().GetQueryParameterSubSchema().GetName() == "dryRun" {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, errors.New("couldn't find GVK in openapi")
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
/*
|
|
||||||
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 openapi_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSupportsDryRun(t *testing.T) {
|
|
||||||
doc, err := fakeSchema.OpenAPISchema()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to get OpenAPI Schema: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
gvk schema.GroupVersionKind
|
|
||||||
success bool
|
|
||||||
supports bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
gvk: schema.GroupVersionKind{
|
|
||||||
Group: "",
|
|
||||||
Version: "v1",
|
|
||||||
Kind: "Pod",
|
|
||||||
},
|
|
||||||
success: true,
|
|
||||||
supports: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
gvk: schema.GroupVersionKind{
|
|
||||||
Group: "",
|
|
||||||
Version: "v1",
|
|
||||||
Kind: "UnknownKind",
|
|
||||||
},
|
|
||||||
success: false,
|
|
||||||
supports: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
gvk: schema.GroupVersionKind{
|
|
||||||
Group: "",
|
|
||||||
Version: "v1",
|
|
||||||
Kind: "NodeProxyOptions",
|
|
||||||
},
|
|
||||||
success: true,
|
|
||||||
supports: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
supports, err := openapi.SupportsDryRun(doc, test.gvk)
|
|
||||||
if supports != test.supports || ((err == nil) != test.success) {
|
|
||||||
errStr := "nil"
|
|
||||||
if test.success == false {
|
|
||||||
errStr = "err"
|
|
||||||
}
|
|
||||||
t.Errorf("SupportsDryRun(doc, %v) = (%v, %v), expected (%v, %v)",
|
|
||||||
test.gvk,
|
|
||||||
supports, err,
|
|
||||||
test.supports, errStr,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 openapi
|
|
||||||
|
|
||||||
import "github.com/go-openapi/spec"
|
|
||||||
|
|
||||||
const PrintColumnsKey = "x-kubernetes-print-columns"
|
|
||||||
|
|
||||||
// GetPrintColumns looks for the open API extension for the display columns.
|
|
||||||
func GetPrintColumns(extensions spec.Extensions) (string, bool) {
|
|
||||||
return extensions.GetString(PrintColumnsKey)
|
|
||||||
}
|
|
|
@ -1,127 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Resources interface describe a resources provider, that can give you
|
|
||||||
// resource based on group-version-kind.
|
|
||||||
type Resources interface {
|
|
||||||
LookupResource(gvk schema.GroupVersionKind) proto.Schema
|
|
||||||
}
|
|
||||||
|
|
||||||
// groupVersionKindExtensionKey is the key used to lookup the
|
|
||||||
// GroupVersionKind value for an object definition from the
|
|
||||||
// definition's "extensions" map.
|
|
||||||
const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind"
|
|
||||||
|
|
||||||
// document is an implementation of `Resources`. It looks for
|
|
||||||
// resources in an openapi Schema.
|
|
||||||
type document struct {
|
|
||||||
// Maps gvk to model name
|
|
||||||
resources map[schema.GroupVersionKind]string
|
|
||||||
models proto.Models
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Resources = &document{}
|
|
||||||
|
|
||||||
func NewOpenAPIData(doc *openapi_v2.Document) (Resources, error) {
|
|
||||||
models, err := proto.NewOpenAPIData(doc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resources := map[schema.GroupVersionKind]string{}
|
|
||||||
for _, modelName := range models.ListModels() {
|
|
||||||
model := models.LookupModel(modelName)
|
|
||||||
if model == nil {
|
|
||||||
panic("ListModels returns a model that can't be looked-up.")
|
|
||||||
}
|
|
||||||
gvkList := parseGroupVersionKind(model)
|
|
||||||
for _, gvk := range gvkList {
|
|
||||||
if len(gvk.Kind) > 0 {
|
|
||||||
resources[gvk] = modelName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &document{
|
|
||||||
resources: resources,
|
|
||||||
models: models,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *document) LookupResource(gvk schema.GroupVersionKind) proto.Schema {
|
|
||||||
modelName, found := d.resources[gvk]
|
|
||||||
if !found {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return d.models.LookupModel(modelName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one.
|
|
||||||
func parseGroupVersionKind(s proto.Schema) []schema.GroupVersionKind {
|
|
||||||
extensions := s.GetExtensions()
|
|
||||||
|
|
||||||
gvkListResult := []schema.GroupVersionKind{}
|
|
||||||
|
|
||||||
// Get the extensions
|
|
||||||
gvkExtension, ok := extensions[groupVersionKindExtensionKey]
|
|
||||||
if !ok {
|
|
||||||
return []schema.GroupVersionKind{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// gvk extension must be a list of at least 1 element.
|
|
||||||
gvkList, ok := gvkExtension.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return []schema.GroupVersionKind{}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, gvk := range gvkList {
|
|
||||||
// gvk extension list must be a map with group, version, and
|
|
||||||
// kind fields
|
|
||||||
gvkMap, ok := gvk.(map[interface{}]interface{})
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
group, ok := gvkMap["group"].(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
version, ok := gvkMap["version"].(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
kind, ok := gvkMap["kind"].(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
gvkListResult = append(gvkListResult, schema.GroupVersionKind{
|
|
||||||
Group: group,
|
|
||||||
Version: version,
|
|
||||||
Kind: kind,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return gvkListResult
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"k8s.io/client-go/discovery"
|
|
||||||
)
|
|
||||||
|
|
||||||
// synchronizedOpenAPIGetter fetches the openapi schema once and then caches it in memory
|
|
||||||
type synchronizedOpenAPIGetter struct {
|
|
||||||
// Cached results
|
|
||||||
sync.Once
|
|
||||||
openAPISchema Resources
|
|
||||||
err error
|
|
||||||
|
|
||||||
openAPIClient discovery.OpenAPISchemaInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Getter = &synchronizedOpenAPIGetter{}
|
|
||||||
|
|
||||||
// Getter is an interface for fetching openapi specs and parsing them into an Resources struct
|
|
||||||
type Getter interface {
|
|
||||||
// OpenAPIData returns the parsed OpenAPIData
|
|
||||||
Get() (Resources, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOpenAPIGetter returns an object to return OpenAPIDatas which reads
|
|
||||||
// from a server, and then stores in memory for subsequent invocations
|
|
||||||
func NewOpenAPIGetter(openAPIClient discovery.OpenAPISchemaInterface) Getter {
|
|
||||||
return &synchronizedOpenAPIGetter{
|
|
||||||
openAPIClient: openAPIClient,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resources implements Getter
|
|
||||||
func (g *synchronizedOpenAPIGetter) Get() (Resources, error) {
|
|
||||||
g.Do(func() {
|
|
||||||
s, err := g.openAPIClient.OpenAPISchema()
|
|
||||||
if err != nil {
|
|
||||||
g.err = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
g.openAPISchema, g.err = NewOpenAPIData(s)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Return the save result
|
|
||||||
return g.openAPISchema, g.err
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 openapi_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FakeCounter returns a "null" document and the specified error. It
|
|
||||||
// also counts how many times the OpenAPISchema method has been called.
|
|
||||||
type FakeCounter struct {
|
|
||||||
Calls int
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeCounter) OpenAPISchema() (*openapi_v2.Document, error) {
|
|
||||||
f.Calls = f.Calls + 1
|
|
||||||
return nil, f.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = Describe("Getting the Resources", func() {
|
|
||||||
var client FakeCounter
|
|
||||||
var instance openapi.Getter
|
|
||||||
var expectedData openapi.Resources
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
client = FakeCounter{}
|
|
||||||
instance = openapi.NewOpenAPIGetter(&client)
|
|
||||||
var err error
|
|
||||||
expectedData, err = openapi.NewOpenAPIData(nil)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("when the server returns a successful result", func() {
|
|
||||||
It("should return the same data for multiple calls", func() {
|
|
||||||
Expect(client.Calls).To(Equal(0))
|
|
||||||
|
|
||||||
result, err := instance.Get()
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
Expect(result).To(Equal(expectedData))
|
|
||||||
Expect(client.Calls).To(Equal(1))
|
|
||||||
|
|
||||||
result, err = instance.Get()
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
Expect(result).To(Equal(expectedData))
|
|
||||||
// No additional client calls expected
|
|
||||||
Expect(client.Calls).To(Equal(1))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("when the server returns an unsuccessful result", func() {
|
|
||||||
It("should return the same instance for multiple calls.", func() {
|
|
||||||
Expect(client.Calls).To(Equal(0))
|
|
||||||
|
|
||||||
client.Err = fmt.Errorf("expected error")
|
|
||||||
_, err := instance.Get()
|
|
||||||
Expect(err).To(Equal(client.Err))
|
|
||||||
Expect(client.Calls).To(Equal(1))
|
|
||||||
|
|
||||||
_, err = instance.Get()
|
|
||||||
Expect(err).To(Equal(client.Err))
|
|
||||||
// No additional client calls expected
|
|
||||||
Expect(client.Calls).To(Equal(1))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,49 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 openapi_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/ginkgo/config"
|
|
||||||
. "github.com/onsi/ginkgo/types"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestOpenapi(t *testing.T) {
|
|
||||||
RegisterFailHandler(Fail)
|
|
||||||
RunSpecsWithDefaultAndCustomReporters(t, "Openapi Suite", []Reporter{newlineReporter{}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print a newline after the default newlineReporter due to issue
|
|
||||||
// https://github.com/jstemmer/go-junit-report/issues/31
|
|
||||||
type newlineReporter struct{}
|
|
||||||
|
|
||||||
func (newlineReporter) SpecSuiteWillBegin(config GinkgoConfigType, summary *SuiteSummary) {}
|
|
||||||
|
|
||||||
func (newlineReporter) BeforeSuiteDidRun(setupSummary *SetupSummary) {}
|
|
||||||
|
|
||||||
func (newlineReporter) AfterSuiteDidRun(setupSummary *SetupSummary) {}
|
|
||||||
|
|
||||||
func (newlineReporter) SpecWillRun(specSummary *SpecSummary) {}
|
|
||||||
|
|
||||||
func (newlineReporter) SpecDidComplete(specSummary *SpecSummary) {}
|
|
||||||
|
|
||||||
// SpecSuiteDidEnd Prints a newline between "35 Passed | 0 Failed | 0 Pending | 0 Skipped" and "--- PASS:"
|
|
||||||
func (newlineReporter) SpecSuiteDidEnd(summary *SuiteSummary) { fmt.Printf("\n") }
|
|
|
@ -1,93 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 openapi_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto/testing"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
|
||||||
)
|
|
||||||
|
|
||||||
var fakeSchema = testing.Fake{Path: filepath.Join("..", "..", "..", "..", "..", "api", "openapi-spec", "swagger.json")}
|
|
||||||
|
|
||||||
var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() {
|
|
||||||
var resources openapi.Resources
|
|
||||||
BeforeEach(func() {
|
|
||||||
s, err := fakeSchema.OpenAPISchema()
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
resources, err = openapi.NewOpenAPIData(s)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
gvk := schema.GroupVersionKind{
|
|
||||||
Kind: "Deployment",
|
|
||||||
Version: "v1beta1",
|
|
||||||
Group: "apps",
|
|
||||||
}
|
|
||||||
|
|
||||||
var schema proto.Schema
|
|
||||||
It("should lookup the Schema by its GroupVersionKind", func() {
|
|
||||||
schema = resources.LookupResource(gvk)
|
|
||||||
Expect(schema).ToNot(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
var deployment *proto.Kind
|
|
||||||
It("should be a Kind", func() {
|
|
||||||
deployment = schema.(*proto.Kind)
|
|
||||||
Expect(deployment).ToNot(BeNil())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
var _ = Describe("Reading authorization.k8s.io/v1/SubjectAccessReview from openAPIData", func() {
|
|
||||||
var resources openapi.Resources
|
|
||||||
BeforeEach(func() {
|
|
||||||
s, err := fakeSchema.OpenAPISchema()
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
resources, err = openapi.NewOpenAPIData(s)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
gvk := schema.GroupVersionKind{
|
|
||||||
Kind: "SubjectAccessReview",
|
|
||||||
Version: "v1",
|
|
||||||
Group: "authorization.k8s.io",
|
|
||||||
}
|
|
||||||
|
|
||||||
var schema proto.Schema
|
|
||||||
It("should lookup the Schema by its GroupVersionKind", func() {
|
|
||||||
schema = resources.LookupResource(gvk)
|
|
||||||
Expect(schema).ToNot(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
var sarspec *proto.Kind
|
|
||||||
It("should be a Kind and have a spec", func() {
|
|
||||||
sar := schema.(*proto.Kind)
|
|
||||||
Expect(sar).ToNot(BeNil())
|
|
||||||
Expect(sar.Fields).To(HaveKey("spec"))
|
|
||||||
specRef := sar.Fields["spec"].(proto.Reference)
|
|
||||||
Expect(specRef).ToNot(BeNil())
|
|
||||||
Expect(specRef.Reference()).To(Equal("io.k8s.api.authorization.v1.SubjectAccessReviewSpec"))
|
|
||||||
sarspec = specRef.SubSchema().(*proto.Kind)
|
|
||||||
Expect(sarspec).ToNot(BeNil())
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,31 +0,0 @@
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["openapi.go"],
|
|
||||||
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing",
|
|
||||||
deps = [
|
|
||||||
"//pkg/kubectl/cmd/util/openapi:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/util/proto/testing:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
|
@ -1,71 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 testing
|
|
||||||
|
|
||||||
import (
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto/testing"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FakeResources is a wrapper to directly load the openapi schema from a
|
|
||||||
// file, and get the schema for given GVK. This is only for test since
|
|
||||||
// it's assuming that the file is there and everything will go fine.
|
|
||||||
type FakeResources struct {
|
|
||||||
fake testing.Fake
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ openapi.Resources = &FakeResources{}
|
|
||||||
|
|
||||||
// NewFakeResources creates a new FakeResources.
|
|
||||||
func NewFakeResources(path string) *FakeResources {
|
|
||||||
return &FakeResources{
|
|
||||||
fake: testing.Fake{Path: path},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LookupResource will read the schema, parse it and return the
|
|
||||||
// resources. It doesn't return errors and will panic instead.
|
|
||||||
func (f *FakeResources) LookupResource(gvk schema.GroupVersionKind) proto.Schema {
|
|
||||||
s, err := f.fake.OpenAPISchema()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
resources, err := openapi.NewOpenAPIData(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return resources.LookupResource(gvk)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmptyResources implement a Resources that just doesn't have any resources.
|
|
||||||
type EmptyResources struct{}
|
|
||||||
|
|
||||||
var _ openapi.Resources = EmptyResources{}
|
|
||||||
|
|
||||||
// LookupResource will always return nil. It doesn't have any resources.
|
|
||||||
func (f EmptyResources) LookupResource(gvk schema.GroupVersionKind) proto.Schema {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOpenAPISchemaFunc returns a function useful for the TestFactory.
|
|
||||||
func CreateOpenAPISchemaFunc(path string) func() (openapi.Resources, error) {
|
|
||||||
return func() (openapi.Resources, error) {
|
|
||||||
return NewFakeResources(path), nil
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
"go_test",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["validation.go"],
|
|
||||||
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation",
|
|
||||||
deps = [
|
|
||||||
"//pkg/kubectl/cmd/util/openapi:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/util/proto/validation:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = [
|
|
||||||
"validation_suite_test.go",
|
|
||||||
"validation_test.go",
|
|
||||||
],
|
|
||||||
data = ["//api/openapi-spec:swagger-spec"],
|
|
||||||
embed = [":go_default_library"],
|
|
||||||
deps = [
|
|
||||||
"//pkg/kubectl/cmd/util/openapi:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
|
||||||
"//vendor/github.com/onsi/ginkgo:go_default_library",
|
|
||||||
"//vendor/github.com/onsi/ginkgo/config:go_default_library",
|
|
||||||
"//vendor/github.com/onsi/ginkgo/types:go_default_library",
|
|
||||||
"//vendor/github.com/onsi/gomega:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/util/proto/testing:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/util/proto/validation:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
|
@ -1,140 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 validation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
|
||||||
"k8s.io/apimachinery/pkg/util/json"
|
|
||||||
"k8s.io/apimachinery/pkg/util/yaml"
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto/validation"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SchemaValidation validates the object against an OpenAPI schema.
|
|
||||||
type SchemaValidation struct {
|
|
||||||
resources openapi.Resources
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSchemaValidation creates a new SchemaValidation that can be used
|
|
||||||
// to validate objects.
|
|
||||||
func NewSchemaValidation(resources openapi.Resources) *SchemaValidation {
|
|
||||||
return &SchemaValidation{
|
|
||||||
resources: resources,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateBytes will validates the object against using the Resources
|
|
||||||
// object.
|
|
||||||
func (v *SchemaValidation) ValidateBytes(data []byte) error {
|
|
||||||
obj, err := parse(data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
gvk, errs := getObjectKind(obj)
|
|
||||||
if errs != nil {
|
|
||||||
return utilerrors.NewAggregate(errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gvk == schema.GroupVersionKind{Version: "v1", Kind: "List"}) {
|
|
||||||
return utilerrors.NewAggregate(v.validateList(obj))
|
|
||||||
}
|
|
||||||
|
|
||||||
return utilerrors.NewAggregate(v.validateResource(obj, gvk))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *SchemaValidation) validateList(object interface{}) []error {
|
|
||||||
fields, ok := object.(map[string]interface{})
|
|
||||||
if !ok || fields == nil {
|
|
||||||
return []error{errors.New("invalid object to validate")}
|
|
||||||
}
|
|
||||||
|
|
||||||
allErrors := []error{}
|
|
||||||
if _, ok := fields["items"].([]interface{}); !ok {
|
|
||||||
return []error{errors.New("invalid object to validate")}
|
|
||||||
}
|
|
||||||
for _, item := range fields["items"].([]interface{}) {
|
|
||||||
if gvk, errs := getObjectKind(item); errs != nil {
|
|
||||||
allErrors = append(allErrors, errs...)
|
|
||||||
} else {
|
|
||||||
allErrors = append(allErrors, v.validateResource(item, gvk)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return allErrors
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *SchemaValidation) validateResource(obj interface{}, gvk schema.GroupVersionKind) []error {
|
|
||||||
resource := v.resources.LookupResource(gvk)
|
|
||||||
if resource == nil {
|
|
||||||
// resource is not present, let's just skip validation.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return validation.ValidateModel(obj, resource, gvk.Kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parse(data []byte) (interface{}, error) {
|
|
||||||
var obj interface{}
|
|
||||||
out, err := yaml.ToJSON(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(out, &obj); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return obj, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getObjectKind(object interface{}) (schema.GroupVersionKind, []error) {
|
|
||||||
var listErrors []error
|
|
||||||
fields, ok := object.(map[string]interface{})
|
|
||||||
if !ok || fields == nil {
|
|
||||||
listErrors = append(listErrors, errors.New("invalid object to validate"))
|
|
||||||
return schema.GroupVersionKind{}, listErrors
|
|
||||||
}
|
|
||||||
|
|
||||||
var group string
|
|
||||||
var version string
|
|
||||||
apiVersion := fields["apiVersion"]
|
|
||||||
if apiVersion == nil {
|
|
||||||
listErrors = append(listErrors, errors.New("apiVersion not set"))
|
|
||||||
} else if _, ok := apiVersion.(string); !ok {
|
|
||||||
listErrors = append(listErrors, errors.New("apiVersion isn't string type"))
|
|
||||||
} else {
|
|
||||||
gv, err := schema.ParseGroupVersion(apiVersion.(string))
|
|
||||||
if err != nil {
|
|
||||||
listErrors = append(listErrors, err)
|
|
||||||
} else {
|
|
||||||
group = gv.Group
|
|
||||||
version = gv.Version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
kind := fields["kind"]
|
|
||||||
if kind == nil {
|
|
||||||
listErrors = append(listErrors, errors.New("kind not set"))
|
|
||||||
} else if _, ok := kind.(string); !ok {
|
|
||||||
listErrors = append(listErrors, errors.New("kind isn't string type"))
|
|
||||||
}
|
|
||||||
if listErrors != nil {
|
|
||||||
return schema.GroupVersionKind{}, listErrors
|
|
||||||
}
|
|
||||||
|
|
||||||
return schema.GroupVersionKind{Group: group, Version: version, Kind: kind.(string)}, nil
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 validation
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/ginkgo/config"
|
|
||||||
. "github.com/onsi/ginkgo/types"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestOpenapi(t *testing.T) {
|
|
||||||
RegisterFailHandler(Fail)
|
|
||||||
RunSpecsWithDefaultAndCustomReporters(t, "Openapi Suite", []Reporter{newlineReporter{}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print a newline after the default newlineReporter due to issue
|
|
||||||
// https://github.com/jstemmer/go-junit-report/issues/31
|
|
||||||
type newlineReporter struct{}
|
|
||||||
|
|
||||||
func (newlineReporter) SpecSuiteWillBegin(config GinkgoConfigType, summary *SuiteSummary) {}
|
|
||||||
|
|
||||||
func (newlineReporter) BeforeSuiteDidRun(setupSummary *SetupSummary) {}
|
|
||||||
|
|
||||||
func (newlineReporter) AfterSuiteDidRun(setupSummary *SetupSummary) {}
|
|
||||||
|
|
||||||
func (newlineReporter) SpecWillRun(specSummary *SpecSummary) {}
|
|
||||||
|
|
||||||
func (newlineReporter) SpecDidComplete(specSummary *SpecSummary) {}
|
|
||||||
|
|
||||||
// SpecSuiteDidEnd Prints a newline between "35 Passed | 0 Failed | 0 Pending | 0 Skipped" and "--- PASS:"
|
|
||||||
func (newlineReporter) SpecSuiteDidEnd(summary *SuiteSummary) { fmt.Printf("\n") }
|
|
|
@ -1,408 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 validation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
|
|
||||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto/validation"
|
|
||||||
// This dependency is needed to register API types.
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto/testing"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
|
||||||
)
|
|
||||||
|
|
||||||
var fakeSchema = testing.Fake{Path: filepath.Join("..", "..", "..", "..", "..", "..", "api", "openapi-spec", "swagger.json")}
|
|
||||||
|
|
||||||
var _ = Describe("resource validation using OpenAPI Schema", func() {
|
|
||||||
var validator *SchemaValidation
|
|
||||||
BeforeEach(func() {
|
|
||||||
s, err := fakeSchema.OpenAPISchema()
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
resources, err := openapi.NewOpenAPIData(s)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
validator = NewSchemaValidation(resources)
|
|
||||||
Expect(validator).ToNot(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("finds Deployment in Schema and validates it", func() {
|
|
||||||
err := validator.ValidateBytes([]byte(`
|
|
||||||
apiVersion: extensions/v1beta1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
name: redis-master
|
|
||||||
name: name
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: redis
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: redis
|
|
||||||
name: redis
|
|
||||||
`))
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("validates a valid pod", func() {
|
|
||||||
err := validator.ValidateBytes([]byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
name: redis-master
|
|
||||||
name: name
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- args:
|
|
||||||
- this
|
|
||||||
- is
|
|
||||||
- an
|
|
||||||
- ok
|
|
||||||
- command
|
|
||||||
image: gcr.io/fake_project/fake_image:fake_tag
|
|
||||||
name: master
|
|
||||||
`))
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("finds invalid command (string instead of []string) in Json Pod", func() {
|
|
||||||
err := validator.ValidateBytes([]byte(`
|
|
||||||
{
|
|
||||||
"kind": "Pod",
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"metadata": {
|
|
||||||
"name": "name",
|
|
||||||
"labels": {
|
|
||||||
"name": "redis-master"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"containers": [
|
|
||||||
{
|
|
||||||
"name": "master",
|
|
||||||
"image": "gcr.io/fake_project/fake_image:fake_tag",
|
|
||||||
"args": "this is a bad command"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`))
|
|
||||||
Expect(err).To(Equal(utilerrors.NewAggregate([]error{
|
|
||||||
validation.ValidationError{
|
|
||||||
Path: "Pod.spec.containers[0].args",
|
|
||||||
Err: validation.InvalidTypeError{
|
|
||||||
Path: "io.k8s.api.core.v1.Container.args",
|
|
||||||
Expected: "array",
|
|
||||||
Actual: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("fails because hostPort is string instead of int", func() {
|
|
||||||
err := validator.ValidateBytes([]byte(`
|
|
||||||
{
|
|
||||||
"kind": "Pod",
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"metadata": {
|
|
||||||
"name": "apache-php",
|
|
||||||
"labels": {
|
|
||||||
"name": "apache-php"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"volumes": [{
|
|
||||||
"name": "shared-disk"
|
|
||||||
}],
|
|
||||||
"containers": [
|
|
||||||
{
|
|
||||||
"name": "apache-php",
|
|
||||||
"image": "gcr.io/fake_project/fake_image:fake_tag",
|
|
||||||
"ports": [
|
|
||||||
{
|
|
||||||
"name": "apache",
|
|
||||||
"hostPort": "13380",
|
|
||||||
"containerPort": 80,
|
|
||||||
"protocol": "TCP"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"volumeMounts": [
|
|
||||||
{
|
|
||||||
"name": "shared-disk",
|
|
||||||
"mountPath": "/var/www/html"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`))
|
|
||||||
|
|
||||||
Expect(err).To(Equal(utilerrors.NewAggregate([]error{
|
|
||||||
validation.ValidationError{
|
|
||||||
Path: "Pod.spec.containers[0].ports[0].hostPort",
|
|
||||||
Err: validation.InvalidTypeError{
|
|
||||||
Path: "io.k8s.api.core.v1.ContainerPort.hostPort",
|
|
||||||
Expected: "integer",
|
|
||||||
Actual: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})))
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
It("fails because volume is not an array of object", func() {
|
|
||||||
err := validator.ValidateBytes([]byte(`
|
|
||||||
{
|
|
||||||
"kind": "Pod",
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"metadata": {
|
|
||||||
"name": "apache-php",
|
|
||||||
"labels": {
|
|
||||||
"name": "apache-php"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"volumes": [
|
|
||||||
"name": "shared-disk"
|
|
||||||
],
|
|
||||||
"containers": [
|
|
||||||
{
|
|
||||||
"name": "apache-php",
|
|
||||||
"image": "gcr.io/fake_project/fake_image:fake_tag",
|
|
||||||
"ports": [
|
|
||||||
{
|
|
||||||
"name": "apache",
|
|
||||||
"hostPort": 13380,
|
|
||||||
"containerPort": 80,
|
|
||||||
"protocol": "TCP"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"volumeMounts": [
|
|
||||||
{
|
|
||||||
"name": "shared-disk",
|
|
||||||
"mountPath": "/var/www/html"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`))
|
|
||||||
Expect(err.Error()).To(Equal("invalid character ':' after array element"))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("fails because some string lists have empty strings", func() {
|
|
||||||
err := validator.ValidateBytes([]byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
name: redis-master
|
|
||||||
name: name
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: gcr.io/fake_project/fake_image:fake_tag
|
|
||||||
name: master
|
|
||||||
args:
|
|
||||||
-
|
|
||||||
command:
|
|
||||||
-
|
|
||||||
`))
|
|
||||||
|
|
||||||
Expect(err).To(Equal(utilerrors.NewAggregate([]error{
|
|
||||||
validation.ValidationError{
|
|
||||||
Path: "Pod.spec.containers[0].args",
|
|
||||||
Err: validation.InvalidObjectTypeError{
|
|
||||||
Path: "Pod.spec.containers[0].args[0]",
|
|
||||||
Type: "nil",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
validation.ValidationError{
|
|
||||||
Path: "Pod.spec.containers[0].command",
|
|
||||||
Err: validation.InvalidObjectTypeError{
|
|
||||||
Path: "Pod.spec.containers[0].command[0]",
|
|
||||||
Type: "nil",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("fails if required fields are missing", func() {
|
|
||||||
err := validator.ValidateBytes([]byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
name: redis-master
|
|
||||||
name: name
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- command: ["my", "command"]
|
|
||||||
`))
|
|
||||||
|
|
||||||
Expect(err).To(Equal(utilerrors.NewAggregate([]error{
|
|
||||||
validation.ValidationError{
|
|
||||||
Path: "Pod.spec.containers[0]",
|
|
||||||
Err: validation.MissingRequiredFieldError{
|
|
||||||
Path: "io.k8s.api.core.v1.Container",
|
|
||||||
Field: "name",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("fails if required fields are empty", func() {
|
|
||||||
err := validator.ValidateBytes([]byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
name: redis-master
|
|
||||||
name: name
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image:
|
|
||||||
name:
|
|
||||||
`))
|
|
||||||
|
|
||||||
Expect(err).To(Equal(utilerrors.NewAggregate([]error{
|
|
||||||
validation.ValidationError{
|
|
||||||
Path: "Pod.spec.containers[0]",
|
|
||||||
Err: validation.MissingRequiredFieldError{
|
|
||||||
Path: "io.k8s.api.core.v1.Container",
|
|
||||||
Field: "name",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("is fine with empty non-mandatory fields", func() {
|
|
||||||
err := validator.ValidateBytes([]byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
name: redis-master
|
|
||||||
name: name
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: image
|
|
||||||
name: name
|
|
||||||
command:
|
|
||||||
`))
|
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("can validate lists", func() {
|
|
||||||
err := validator.ValidateBytes([]byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: List
|
|
||||||
items:
|
|
||||||
- apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
name: redis-master
|
|
||||||
name: name
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: name
|
|
||||||
`))
|
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("fails because apiVersion is not provided", func() {
|
|
||||||
err := validator.ValidateBytes([]byte(`
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
name: name
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: name
|
|
||||||
image: image
|
|
||||||
`))
|
|
||||||
Expect(err.Error()).To(Equal("apiVersion not set"))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("fails because apiVersion type is not string and kind is not provided", func() {
|
|
||||||
err := validator.ValidateBytes([]byte(`
|
|
||||||
apiVersion: 1
|
|
||||||
metadata:
|
|
||||||
name: name
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: name
|
|
||||||
image: image
|
|
||||||
`))
|
|
||||||
Expect(err.Error()).To(Equal("[apiVersion isn't string type, kind not set]"))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("fails because List first item is missing kind and second item is missing apiVersion", func() {
|
|
||||||
err := validator.ValidateBytes([]byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: List
|
|
||||||
items:
|
|
||||||
- apiVersion: v1
|
|
||||||
metadata:
|
|
||||||
name: name
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
name: name
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: name
|
|
||||||
image: image
|
|
||||||
- kind: Service
|
|
||||||
metadata:
|
|
||||||
name: name
|
|
||||||
spec:
|
|
||||||
type: NodePort
|
|
||||||
ports:
|
|
||||||
- port: 123
|
|
||||||
targetPort: 1234
|
|
||||||
name: name
|
|
||||||
selector:
|
|
||||||
name: name
|
|
||||||
`))
|
|
||||||
Expect(err.Error()).To(Equal("[kind not set, apiVersion not set]"))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("is fine with crd resource with List as a suffix kind name, which may not be a list of resources", func() {
|
|
||||||
err := validator.ValidateBytes([]byte(`
|
|
||||||
apiVersion: fake.com/v1
|
|
||||||
kind: FakeList
|
|
||||||
metadata:
|
|
||||||
name: fake
|
|
||||||
spec:
|
|
||||||
foo: bar
|
|
||||||
`))
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,69 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 explain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fieldsPrinter interface {
|
|
||||||
PrintFields(proto.Schema) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitDotNotation(model string) (string, []string) {
|
|
||||||
var fieldsPath []string
|
|
||||||
|
|
||||||
// ignore trailing period
|
|
||||||
model = strings.TrimSuffix(model, ".")
|
|
||||||
|
|
||||||
dotModel := strings.Split(model, ".")
|
|
||||||
if len(dotModel) >= 1 {
|
|
||||||
fieldsPath = dotModel[1:]
|
|
||||||
}
|
|
||||||
return dotModel[0], fieldsPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitAndParseResourceRequest separates the users input into a model and fields
|
|
||||||
func SplitAndParseResourceRequest(inResource string, mapper meta.RESTMapper) (string, []string, error) {
|
|
||||||
inResource, fieldsPath := splitDotNotation(inResource)
|
|
||||||
inResource, _ = mapper.ResourceSingularizer(inResource)
|
|
||||||
return inResource, fieldsPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintModelDescription prints the description of a specific model or dot path.
|
|
||||||
// If recursive, all components nested within the fields of the schema will be
|
|
||||||
// printed.
|
|
||||||
func PrintModelDescription(fieldsPath []string, w io.Writer, schema proto.Schema, gvk schema.GroupVersionKind, recursive bool) error {
|
|
||||||
fieldName := ""
|
|
||||||
if len(fieldsPath) != 0 {
|
|
||||||
fieldName = fieldsPath[len(fieldsPath)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go down the fieldsPath to find what we're trying to explain
|
|
||||||
schema, err := LookupSchemaForField(schema, fieldsPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b := fieldsPrinterBuilder{Recursive: recursive}
|
|
||||||
f := &Formatter{Writer: w, Wrap: 80}
|
|
||||||
return PrintModel(fieldName, f, b, schema, gvk)
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 explain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta/testrestmapper"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSplitAndParseResourceRequest(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
inresource string
|
|
||||||
|
|
||||||
expectedInResource string
|
|
||||||
expectedFieldsPath []string
|
|
||||||
expectedErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no trailing period",
|
|
||||||
inresource: "field1.field2.field3",
|
|
||||||
|
|
||||||
expectedInResource: "field1",
|
|
||||||
expectedFieldsPath: []string{"field2", "field3"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "trailing period with correct fieldsPath",
|
|
||||||
inresource: "field1.field2.field3.",
|
|
||||||
|
|
||||||
expectedInResource: "field1",
|
|
||||||
expectedFieldsPath: []string{"field2", "field3"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "trailing period with incorrect fieldsPath",
|
|
||||||
inresource: "field1.field2.field3.",
|
|
||||||
|
|
||||||
expectedInResource: "field1",
|
|
||||||
expectedFieldsPath: []string{"field2", "field3", ""},
|
|
||||||
expectedErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
mapper := testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
gotInResource, gotFieldsPath, err := SplitAndParseResourceRequest(tt.inresource, mapper)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(tt.expectedInResource, gotInResource) && !tt.expectedErr {
|
|
||||||
t.Errorf("%s: expected inresource: %s, got: %s", tt.name, tt.expectedInResource, gotInResource)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(tt.expectedFieldsPath, gotFieldsPath) && !tt.expectedErr {
|
|
||||||
t.Errorf("%s: expected fieldsPath: %s, got: %s", tt.name, tt.expectedFieldsPath, gotFieldsPath)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 explain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// fieldLookup walks through a schema by following a path, and returns
|
|
||||||
// the final schema.
|
|
||||||
type fieldLookup struct {
|
|
||||||
// Path to walk
|
|
||||||
Path []string
|
|
||||||
|
|
||||||
// Return information: Schema found, or error.
|
|
||||||
Schema proto.Schema
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveLeafSchema is used to detect if we are done walking the path, and
|
|
||||||
// saves the schema as a match.
|
|
||||||
func (f *fieldLookup) SaveLeafSchema(schema proto.Schema) bool {
|
|
||||||
if len(f.Path) != 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Schema = schema
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitArray is mostly a passthrough.
|
|
||||||
func (f *fieldLookup) VisitArray(a *proto.Array) {
|
|
||||||
if f.SaveLeafSchema(a) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Passthrough arrays.
|
|
||||||
a.SubType.Accept(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitMap is mostly a passthrough.
|
|
||||||
func (f *fieldLookup) VisitMap(m *proto.Map) {
|
|
||||||
if f.SaveLeafSchema(m) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Passthrough maps.
|
|
||||||
m.SubType.Accept(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitPrimitive stops the operation and returns itself as the found
|
|
||||||
// schema, even if it had more path to walk.
|
|
||||||
func (f *fieldLookup) VisitPrimitive(p *proto.Primitive) {
|
|
||||||
// Even if Path is not empty (we're not expecting a leaf),
|
|
||||||
// return that primitive.
|
|
||||||
f.Schema = p
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitKind unstacks fields as it finds them.
|
|
||||||
func (f *fieldLookup) VisitKind(k *proto.Kind) {
|
|
||||||
if f.SaveLeafSchema(k) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
subSchema, ok := k.Fields[f.Path[0]]
|
|
||||||
if !ok {
|
|
||||||
f.Error = fmt.Errorf("field %q does not exist", f.Path[0])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Path = f.Path[1:]
|
|
||||||
subSchema.Accept(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitReference is mostly a passthrough.
|
|
||||||
func (f *fieldLookup) VisitReference(r proto.Reference) {
|
|
||||||
if f.SaveLeafSchema(r) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Passthrough references.
|
|
||||||
r.SubSchema().Accept(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LookupSchemaForField looks for the schema of a given path in a base schema.
|
|
||||||
func LookupSchemaForField(schema proto.Schema, path []string) (proto.Schema, error) {
|
|
||||||
f := &fieldLookup{Path: path}
|
|
||||||
schema.Accept(f)
|
|
||||||
return f.Schema, f.Error
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 explain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFindField(t *testing.T) {
|
|
||||||
schema := resources.LookupResource(schema.GroupVersionKind{
|
|
||||||
Group: "",
|
|
||||||
Version: "v1",
|
|
||||||
Kind: "OneKind",
|
|
||||||
})
|
|
||||||
if schema == nil {
|
|
||||||
t.Fatal("Counldn't find schema v1.OneKind")
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
path []string
|
|
||||||
|
|
||||||
err string
|
|
||||||
expectedPath string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "test1",
|
|
||||||
path: []string{},
|
|
||||||
expectedPath: "OneKind",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test2",
|
|
||||||
path: []string{"field1"},
|
|
||||||
expectedPath: "OneKind.field1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test3",
|
|
||||||
path: []string{"field1", "array"},
|
|
||||||
expectedPath: "OtherKind.array",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test4",
|
|
||||||
path: []string{"field1", "what?"},
|
|
||||||
err: `field "what?" does not exist`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test5",
|
|
||||||
path: []string{"field1", ""},
|
|
||||||
err: `field "" does not exist`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
path, err := LookupSchemaForField(schema, tt.path)
|
|
||||||
|
|
||||||
gotErr := ""
|
|
||||||
if err != nil {
|
|
||||||
gotErr = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
gotPath := ""
|
|
||||||
if path != nil {
|
|
||||||
gotPath = path.GetPath().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
if gotErr != tt.err || gotPath != tt.expectedPath {
|
|
||||||
t.Errorf("LookupSchemaForField(schema, %v) = (path: %q, err: %q), expected (path: %q, err: %q)",
|
|
||||||
tt.path, gotPath, gotErr, tt.expectedPath, tt.err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 explain
|
|
||||||
|
|
||||||
import "k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
|
|
||||||
// indentDesc is the level of indentation for descriptions.
|
|
||||||
const indentDesc = 2
|
|
||||||
|
|
||||||
// regularFieldsPrinter prints fields with their type and description.
|
|
||||||
type regularFieldsPrinter struct {
|
|
||||||
Writer *Formatter
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ proto.SchemaVisitor = ®ularFieldsPrinter{}
|
|
||||||
var _ fieldsPrinter = ®ularFieldsPrinter{}
|
|
||||||
|
|
||||||
// VisitArray prints a Array type. It is just a passthrough.
|
|
||||||
func (f *regularFieldsPrinter) VisitArray(a *proto.Array) {
|
|
||||||
a.SubType.Accept(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitKind prints a Kind type. It prints each key in the kind, with
|
|
||||||
// the type, the required flag, and the description.
|
|
||||||
func (f *regularFieldsPrinter) VisitKind(k *proto.Kind) {
|
|
||||||
for _, key := range k.Keys() {
|
|
||||||
v := k.Fields[key]
|
|
||||||
required := ""
|
|
||||||
if k.IsRequired(key) {
|
|
||||||
required = " -required-"
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := f.Writer.Write("%s\t<%s>%s", key, GetTypeName(v), required); err != nil {
|
|
||||||
f.Error = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := f.Writer.Indent(indentDesc).WriteWrapped("%s", v.GetDescription()); err != nil {
|
|
||||||
f.Error = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := f.Writer.Write(""); err != nil {
|
|
||||||
f.Error = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitMap prints a Map type. It is just a passthrough.
|
|
||||||
func (f *regularFieldsPrinter) VisitMap(m *proto.Map) {
|
|
||||||
m.SubType.Accept(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitPrimitive prints a Primitive type. It stops the recursion.
|
|
||||||
func (f *regularFieldsPrinter) VisitPrimitive(p *proto.Primitive) {
|
|
||||||
// Nothing to do. Shouldn't really happen.
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitReference prints a Reference type. It is just a passthrough.
|
|
||||||
func (f *regularFieldsPrinter) VisitReference(r proto.Reference) {
|
|
||||||
r.SubSchema().Accept(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintFields will write the types from schema.
|
|
||||||
func (f *regularFieldsPrinter) PrintFields(schema proto.Schema) error {
|
|
||||||
schema.Accept(f)
|
|
||||||
return f.Error
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 explain
|
|
||||||
|
|
||||||
// fieldsPrinterBuilder builds either a regularFieldsPrinter or a
|
|
||||||
// recursiveFieldsPrinter based on the argument.
|
|
||||||
type fieldsPrinterBuilder struct {
|
|
||||||
Recursive bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildFieldsPrinter builds the appropriate fieldsPrinter.
|
|
||||||
func (f fieldsPrinterBuilder) BuildFieldsPrinter(writer *Formatter) fieldsPrinter {
|
|
||||||
if f.Recursive {
|
|
||||||
return &recursiveFieldsPrinter{
|
|
||||||
Writer: writer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ®ularFieldsPrinter{
|
|
||||||
Writer: writer,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 explain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFields(t *testing.T) {
|
|
||||||
schema := resources.LookupResource(schema.GroupVersionKind{
|
|
||||||
Group: "",
|
|
||||||
Version: "v1",
|
|
||||||
Kind: "OneKind",
|
|
||||||
})
|
|
||||||
if schema == nil {
|
|
||||||
t.Fatal("Couldn't find schema v1.OneKind")
|
|
||||||
}
|
|
||||||
|
|
||||||
want := `field1 <Object> -required-
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ut lacus ac
|
|
||||||
enim vulputate imperdiet ac accumsan risus. Integer vel accumsan lectus.
|
|
||||||
Praesent tempus nulla id tortor luctus, quis varius nulla laoreet. Ut orci
|
|
||||||
nisi, suscipit id velit sed, blandit eleifend turpis. Curabitur tempus ante at
|
|
||||||
lectus viverra, a mattis augue euismod. Morbi quam ligula, porttitor sit amet
|
|
||||||
lacus non, interdum pulvinar tortor. Praesent accumsan risus et ipsum dictum,
|
|
||||||
vel ullamcorper lorem egestas.
|
|
||||||
|
|
||||||
field2 <[]map[string]string>
|
|
||||||
This is an array of object of PrimitiveDef
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
f := Formatter{
|
|
||||||
Writer: &buf,
|
|
||||||
Wrap: 80,
|
|
||||||
}
|
|
||||||
s, err := LookupSchemaForField(schema, []string{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Invalid path %v: %v", []string{}, err)
|
|
||||||
}
|
|
||||||
if err := (fieldsPrinterBuilder{Recursive: false}).BuildFieldsPrinter(&f).PrintFields(s); err != nil {
|
|
||||||
t.Fatalf("Failed to print fields: %v", err)
|
|
||||||
}
|
|
||||||
got := buf.String()
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("Got:\n%v\nWant:\n%v\n", buf.String(), want)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,148 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 explain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// fieldIndentLevel is the level of indentation for fields.
|
|
||||||
const fieldIndentLevel = 3
|
|
||||||
|
|
||||||
// descriptionIndentLevel is the level of indentation for the
|
|
||||||
// description.
|
|
||||||
const descriptionIndentLevel = 5
|
|
||||||
|
|
||||||
// modelPrinter prints a schema in Writer. Its "Builder" will decide if
|
|
||||||
// it's recursive or not.
|
|
||||||
type modelPrinter struct {
|
|
||||||
Name string
|
|
||||||
Type string
|
|
||||||
Descriptions []string
|
|
||||||
Writer *Formatter
|
|
||||||
Builder fieldsPrinterBuilder
|
|
||||||
GVK schema.GroupVersionKind
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ proto.SchemaVisitor = &modelPrinter{}
|
|
||||||
|
|
||||||
func (m *modelPrinter) PrintKindAndVersion() error {
|
|
||||||
if err := m.Writer.Write("KIND: %s", m.GVK.Kind); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return m.Writer.Write("VERSION: %s\n", m.GVK.GroupVersion())
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintDescription prints the description for a given schema. There
|
|
||||||
// might be multiple description, since we collect descriptions when we
|
|
||||||
// go through references, arrays and maps.
|
|
||||||
func (m *modelPrinter) PrintDescription(schema proto.Schema) error {
|
|
||||||
if err := m.Writer.Write("DESCRIPTION:"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i, desc := range append(m.Descriptions, schema.GetDescription()) {
|
|
||||||
if desc == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if i != 0 {
|
|
||||||
if err := m.Writer.Write(""); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := m.Writer.Indent(descriptionIndentLevel).WriteWrapped(desc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitArray recurses inside the subtype, while collecting the type if
|
|
||||||
// not done yet, and the description.
|
|
||||||
func (m *modelPrinter) VisitArray(a *proto.Array) {
|
|
||||||
m.Descriptions = append(m.Descriptions, a.GetDescription())
|
|
||||||
if m.Type == "" {
|
|
||||||
m.Type = GetTypeName(a)
|
|
||||||
}
|
|
||||||
a.SubType.Accept(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitKind prints a full resource with its fields.
|
|
||||||
func (m *modelPrinter) VisitKind(k *proto.Kind) {
|
|
||||||
if err := m.PrintKindAndVersion(); err != nil {
|
|
||||||
m.Error = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Type == "" {
|
|
||||||
m.Type = GetTypeName(k)
|
|
||||||
}
|
|
||||||
if m.Name != "" {
|
|
||||||
m.Writer.Write("RESOURCE: %s <%s>\n", m.Name, m.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.PrintDescription(k); err != nil {
|
|
||||||
m.Error = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := m.Writer.Write("\nFIELDS:"); err != nil {
|
|
||||||
m.Error = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.Error = m.Builder.BuildFieldsPrinter(m.Writer.Indent(fieldIndentLevel)).PrintFields(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitMap recurses inside the subtype, while collecting the type if
|
|
||||||
// not done yet, and the description.
|
|
||||||
func (m *modelPrinter) VisitMap(om *proto.Map) {
|
|
||||||
m.Descriptions = append(m.Descriptions, om.GetDescription())
|
|
||||||
if m.Type == "" {
|
|
||||||
m.Type = GetTypeName(om)
|
|
||||||
}
|
|
||||||
om.SubType.Accept(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitPrimitive prints a field type and its description.
|
|
||||||
func (m *modelPrinter) VisitPrimitive(p *proto.Primitive) {
|
|
||||||
if err := m.PrintKindAndVersion(); err != nil {
|
|
||||||
m.Error = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Type == "" {
|
|
||||||
m.Type = GetTypeName(p)
|
|
||||||
}
|
|
||||||
if err := m.Writer.Write("FIELD: %s <%s>\n", m.Name, m.Type); err != nil {
|
|
||||||
m.Error = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.Error = m.PrintDescription(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitReference recurses inside the subtype, while collecting the description.
|
|
||||||
func (m *modelPrinter) VisitReference(r proto.Reference) {
|
|
||||||
m.Descriptions = append(m.Descriptions, r.GetDescription())
|
|
||||||
r.SubSchema().Accept(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintModel prints the description of a schema in writer.
|
|
||||||
func PrintModel(name string, writer *Formatter, builder fieldsPrinterBuilder, schema proto.Schema, gvk schema.GroupVersionKind) error {
|
|
||||||
m := &modelPrinter{Name: name, Writer: writer, Builder: builder, GVK: gvk}
|
|
||||||
schema.Accept(m)
|
|
||||||
return m.Error
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 explain
|
|
||||||
|
|
||||||
import "k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
|
|
||||||
// indentPerLevel is the level of indentation for each field recursion.
|
|
||||||
const indentPerLevel = 3
|
|
||||||
|
|
||||||
// recursiveFieldsPrinter recursively prints all the fields for a given
|
|
||||||
// schema.
|
|
||||||
type recursiveFieldsPrinter struct {
|
|
||||||
Writer *Formatter
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ proto.SchemaVisitor = &recursiveFieldsPrinter{}
|
|
||||||
var _ fieldsPrinter = &recursiveFieldsPrinter{}
|
|
||||||
var visitedReferences = map[string]struct{}{}
|
|
||||||
|
|
||||||
// VisitArray is just a passthrough.
|
|
||||||
func (f *recursiveFieldsPrinter) VisitArray(a *proto.Array) {
|
|
||||||
a.SubType.Accept(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitKind prints all its fields with their type, and then recurses
|
|
||||||
// inside each of these (pre-order).
|
|
||||||
func (f *recursiveFieldsPrinter) VisitKind(k *proto.Kind) {
|
|
||||||
for _, key := range k.Keys() {
|
|
||||||
v := k.Fields[key]
|
|
||||||
f.Writer.Write("%s\t<%s>", key, GetTypeName(v))
|
|
||||||
subFields := &recursiveFieldsPrinter{
|
|
||||||
Writer: f.Writer.Indent(indentPerLevel),
|
|
||||||
}
|
|
||||||
if err := subFields.PrintFields(v); err != nil {
|
|
||||||
f.Error = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitMap is just a passthrough.
|
|
||||||
func (f *recursiveFieldsPrinter) VisitMap(m *proto.Map) {
|
|
||||||
m.SubType.Accept(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitPrimitive does nothing, since it doesn't have sub-fields.
|
|
||||||
func (f *recursiveFieldsPrinter) VisitPrimitive(p *proto.Primitive) {
|
|
||||||
// Nothing to do.
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitReference is just a passthrough.
|
|
||||||
func (f *recursiveFieldsPrinter) VisitReference(r proto.Reference) {
|
|
||||||
if _, ok := visitedReferences[r.Reference()]; ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
visitedReferences[r.Reference()] = struct{}{}
|
|
||||||
r.SubSchema().Accept(f)
|
|
||||||
delete(visitedReferences, r.Reference())
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintFields will recursively print all the fields for the given
|
|
||||||
// schema.
|
|
||||||
func (f *recursiveFieldsPrinter) PrintFields(schema proto.Schema) error {
|
|
||||||
schema.Accept(f)
|
|
||||||
return f.Error
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 explain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRecursiveFields(t *testing.T) {
|
|
||||||
schema := resources.LookupResource(schema.GroupVersionKind{
|
|
||||||
Group: "",
|
|
||||||
Version: "v1",
|
|
||||||
Kind: "OneKind",
|
|
||||||
})
|
|
||||||
if schema == nil {
|
|
||||||
t.Fatal("Couldn't find schema v1.OneKind")
|
|
||||||
}
|
|
||||||
|
|
||||||
want := `field1 <Object>
|
|
||||||
array <[]integer>
|
|
||||||
int <integer>
|
|
||||||
object <map[string]string>
|
|
||||||
primitive <string>
|
|
||||||
string <string>
|
|
||||||
field2 <[]map[string]string>
|
|
||||||
`
|
|
||||||
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
f := Formatter{
|
|
||||||
Writer: &buf,
|
|
||||||
Wrap: 80,
|
|
||||||
}
|
|
||||||
s, err := LookupSchemaForField(schema, []string{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Invalid path %v: %v", []string{}, err)
|
|
||||||
}
|
|
||||||
if err := (fieldsPrinterBuilder{Recursive: true}).BuildFieldsPrinter(&f).PrintFields(s); err != nil {
|
|
||||||
t.Fatalf("Failed to print fields: %v", err)
|
|
||||||
}
|
|
||||||
got := buf.String()
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("Got:\n%v\nWant:\n%v\n", buf.String(), want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRecursiveFieldsWithSelfReferenceObjects(t *testing.T) {
|
|
||||||
var resources = tst.NewFakeResources("test-recursive-swagger.json")
|
|
||||||
schema := resources.LookupResource(schema.GroupVersionKind{
|
|
||||||
Group: "",
|
|
||||||
Version: "v2",
|
|
||||||
Kind: "OneKind",
|
|
||||||
})
|
|
||||||
if schema == nil {
|
|
||||||
t.Fatal("Couldn't find schema v2.OneKind")
|
|
||||||
}
|
|
||||||
|
|
||||||
want := `field1 <Object>
|
|
||||||
referencefield <Object>
|
|
||||||
referencesarray <[]Object>
|
|
||||||
field2 <Object>
|
|
||||||
reference <Object>
|
|
||||||
referencefield <Object>
|
|
||||||
referencesarray <[]Object>
|
|
||||||
string <string>
|
|
||||||
`
|
|
||||||
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
f := Formatter{
|
|
||||||
Writer: &buf,
|
|
||||||
Wrap: 80,
|
|
||||||
}
|
|
||||||
s, err := LookupSchemaForField(schema, []string{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Invalid path %v: %v", []string{}, err)
|
|
||||||
}
|
|
||||||
if err := (fieldsPrinterBuilder{Recursive: true}).BuildFieldsPrinter(&f).PrintFields(s); err != nil {
|
|
||||||
t.Fatalf("Failed to print fields: %v", err)
|
|
||||||
}
|
|
||||||
got := buf.String()
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("Got:\n%v\nWant:\n%v\n", buf.String(), want)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 explain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// typeName finds the name of a schema
|
|
||||||
type typeName struct {
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ proto.SchemaVisitor = &typeName{}
|
|
||||||
|
|
||||||
// VisitArray adds the [] prefix and recurses.
|
|
||||||
func (t *typeName) VisitArray(a *proto.Array) {
|
|
||||||
s := &typeName{}
|
|
||||||
a.SubType.Accept(s)
|
|
||||||
t.Name = fmt.Sprintf("[]%s", s.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitKind just returns "Object".
|
|
||||||
func (t *typeName) VisitKind(k *proto.Kind) {
|
|
||||||
t.Name = "Object"
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitMap adds the map[string] prefix and recurses.
|
|
||||||
func (t *typeName) VisitMap(m *proto.Map) {
|
|
||||||
s := &typeName{}
|
|
||||||
m.SubType.Accept(s)
|
|
||||||
t.Name = fmt.Sprintf("map[string]%s", s.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitPrimitive returns the name of the primitive.
|
|
||||||
func (t *typeName) VisitPrimitive(p *proto.Primitive) {
|
|
||||||
t.Name = p.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitReference is just a passthrough.
|
|
||||||
func (t *typeName) VisitReference(r proto.Reference) {
|
|
||||||
r.SubSchema().Accept(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTypeName returns the type of a schema.
|
|
||||||
func GetTypeName(schema proto.Schema) string {
|
|
||||||
t := &typeName{}
|
|
||||||
schema.Accept(t)
|
|
||||||
return t.Name
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 explain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var resources = tst.NewFakeResources("test-swagger.json")
|
|
||||||
|
|
||||||
func TestReferenceTypename(t *testing.T) {
|
|
||||||
schema := resources.LookupResource(schema.GroupVersionKind{
|
|
||||||
Group: "",
|
|
||||||
Version: "v1",
|
|
||||||
Kind: "OneKind",
|
|
||||||
})
|
|
||||||
if schema == nil {
|
|
||||||
t.Fatal("Couldn't find schema v1.OneKind")
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
path []string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
// Kind is "Object"
|
|
||||||
name: "test1",
|
|
||||||
path: []string{},
|
|
||||||
expected: "Object",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Reference is equal to pointed type "Object"
|
|
||||||
name: "test2",
|
|
||||||
path: []string{"field1"},
|
|
||||||
expected: "Object",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Reference is equal to pointed type "string"
|
|
||||||
name: "test3",
|
|
||||||
path: []string{"field1", "primitive"},
|
|
||||||
expected: "string",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Array of object of reference to string
|
|
||||||
name: "test4",
|
|
||||||
path: []string{"field2"},
|
|
||||||
expected: "[]map[string]string",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Array of integer
|
|
||||||
name: "test5",
|
|
||||||
path: []string{"field1", "array"},
|
|
||||||
expected: "[]integer",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
s, err := LookupSchemaForField(schema, tt.path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Invalid tt.path %v: %v", tt.path, err)
|
|
||||||
}
|
|
||||||
got := GetTypeName(s)
|
|
||||||
if got != tt.expected {
|
|
||||||
t.Errorf("Got %q, expected %q", got, tt.expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -30,7 +30,6 @@ import (
|
||||||
"k8s.io/apiserver/pkg/util/webhook"
|
"k8s.io/apiserver/pkg/util/webhook"
|
||||||
|
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||||
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
|
|
||||||
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
|
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -507,12 +506,6 @@ func ValidateCustomResourceDefinitionValidation(customResourceValidation *apiext
|
||||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(schema, fldPath.Child("openAPIV3Schema"), openAPIV3Schema)...)
|
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(schema, fldPath.Child("openAPIV3Schema"), openAPIV3Schema)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if validation passed otherwise, make sure we can actually construct a schema validator from this custom resource validation.
|
|
||||||
if len(allErrs) == 0 {
|
|
||||||
if _, _, err := apiservervalidation.NewSchemaValidator(customResourceValidation); err != nil {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath, "", fmt.Sprintf("error building validator: %v", err)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,9 +25,6 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
"github.com/go-openapi/strfmt"
|
|
||||||
"github.com/go-openapi/validate"
|
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
|
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
|
@ -58,7 +55,6 @@ import (
|
||||||
|
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/conversion"
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/conversion"
|
||||||
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
|
|
||||||
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion"
|
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion"
|
||||||
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
|
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
|
||||||
"k8s.io/apiextensions-apiserver/pkg/controller/establish"
|
"k8s.io/apiextensions-apiserver/pkg/controller/establish"
|
||||||
|
@ -462,18 +458,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
|
||||||
typer := newUnstructuredObjectTyper(parameterScheme)
|
typer := newUnstructuredObjectTyper(parameterScheme)
|
||||||
creator := unstructuredCreator{}
|
creator := unstructuredCreator{}
|
||||||
|
|
||||||
validationSchema, err := getSchemaForVersion(crd, v.Name)
|
|
||||||
if err != nil {
|
|
||||||
utilruntime.HandleError(err)
|
|
||||||
return nil, fmt.Errorf("the server could not properly serve the CR schema")
|
|
||||||
}
|
|
||||||
validator, _, err := apiservervalidation.NewSchemaValidator(validationSchema)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var statusSpec *apiextensions.CustomResourceSubresourceStatus
|
var statusSpec *apiextensions.CustomResourceSubresourceStatus
|
||||||
var statusValidator *validate.SchemaValidator
|
|
||||||
subresources, err := getSubresourcesForVersion(crd, v.Name)
|
subresources, err := getSubresourcesForVersion(crd, v.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utilruntime.HandleError(err)
|
utilruntime.HandleError(err)
|
||||||
|
@ -481,16 +466,6 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
|
||||||
}
|
}
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Status != nil {
|
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Status != nil {
|
||||||
statusSpec = subresources.Status
|
statusSpec = subresources.Status
|
||||||
// for the status subresource, validate only against the status schema
|
|
||||||
if validationSchema != nil && validationSchema.OpenAPIV3Schema != nil && validationSchema.OpenAPIV3Schema.Properties != nil {
|
|
||||||
if statusSchema, ok := validationSchema.OpenAPIV3Schema.Properties["status"]; ok {
|
|
||||||
openapiSchema := &spec.Schema{}
|
|
||||||
if err := apiservervalidation.ConvertJSONSchemaProps(&statusSchema, openapiSchema); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
statusValidator = validate.NewSchemaValidator(openapiSchema, nil, "", strfmt.Default)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var scaleSpec *apiextensions.CustomResourceSubresourceScale
|
var scaleSpec *apiextensions.CustomResourceSubresourceScale
|
||||||
|
@ -516,8 +491,6 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
|
||||||
typer,
|
typer,
|
||||||
crd.Spec.Scope == apiextensions.NamespaceScoped,
|
crd.Spec.Scope == apiextensions.NamespaceScoped,
|
||||||
kind,
|
kind,
|
||||||
validator,
|
|
||||||
statusValidator,
|
|
||||||
statusSpec,
|
statusSpec,
|
||||||
scaleSpec,
|
scaleSpec,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
"go_test",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["validation.go"],
|
|
||||||
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/validation",
|
|
||||||
importpath = "k8s.io/apiextensions-apiserver/pkg/apiserver/validation",
|
|
||||||
deps = [
|
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
|
|
||||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
|
||||||
"//vendor/github.com/go-openapi/strfmt:go_default_library",
|
|
||||||
"//vendor/github.com/go-openapi/validate:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = ["validation_test.go"],
|
|
||||||
embed = [":go_default_library"],
|
|
||||||
deps = [
|
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/apitesting/fuzzer:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
|
|
||||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
|
@ -1,249 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 validation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
"github.com/go-openapi/strfmt"
|
|
||||||
"github.com/go-openapi/validate"
|
|
||||||
|
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewSchemaValidator creates an openapi schema validator for the given CRD validation.
|
|
||||||
func NewSchemaValidator(customResourceValidation *apiextensions.CustomResourceValidation) (*validate.SchemaValidator, *spec.Schema, error) {
|
|
||||||
// Convert CRD schema to openapi schema
|
|
||||||
openapiSchema := &spec.Schema{}
|
|
||||||
if customResourceValidation != nil {
|
|
||||||
if err := ConvertJSONSchemaProps(customResourceValidation.OpenAPIV3Schema, openapiSchema); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return validate.NewSchemaValidator(openapiSchema, nil, "", strfmt.Default), openapiSchema, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateCustomResource validates the Custom Resource against the schema in the CustomResourceDefinition.
|
|
||||||
// CustomResource is a JSON data structure.
|
|
||||||
func ValidateCustomResource(customResource interface{}, validator *validate.SchemaValidator) error {
|
|
||||||
if validator == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result := validator.Validate(customResource)
|
|
||||||
if result.AsError() != nil {
|
|
||||||
return result.AsError()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertJSONSchemaProps converts the schema from apiextensions.JSONSchemaPropos to go-openapi/spec.Schema
|
|
||||||
func ConvertJSONSchemaProps(in *apiextensions.JSONSchemaProps, out *spec.Schema) error {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
out.ID = in.ID
|
|
||||||
out.Schema = spec.SchemaURL(in.Schema)
|
|
||||||
out.Description = in.Description
|
|
||||||
if in.Type != "" {
|
|
||||||
out.Type = spec.StringOrArray([]string{in.Type})
|
|
||||||
}
|
|
||||||
out.Format = in.Format
|
|
||||||
out.Title = in.Title
|
|
||||||
out.Maximum = in.Maximum
|
|
||||||
out.ExclusiveMaximum = in.ExclusiveMaximum
|
|
||||||
out.Minimum = in.Minimum
|
|
||||||
out.ExclusiveMinimum = in.ExclusiveMinimum
|
|
||||||
out.MaxLength = in.MaxLength
|
|
||||||
out.MinLength = in.MinLength
|
|
||||||
out.Pattern = in.Pattern
|
|
||||||
out.MaxItems = in.MaxItems
|
|
||||||
out.MinItems = in.MinItems
|
|
||||||
out.UniqueItems = in.UniqueItems
|
|
||||||
out.MultipleOf = in.MultipleOf
|
|
||||||
out.MaxProperties = in.MaxProperties
|
|
||||||
out.MinProperties = in.MinProperties
|
|
||||||
out.Required = in.Required
|
|
||||||
|
|
||||||
if in.Default != nil {
|
|
||||||
out.Default = *(in.Default)
|
|
||||||
}
|
|
||||||
if in.Example != nil {
|
|
||||||
out.Example = *(in.Example)
|
|
||||||
}
|
|
||||||
|
|
||||||
out.Enum = make([]interface{}, len(in.Enum))
|
|
||||||
for k, v := range in.Enum {
|
|
||||||
out.Enum[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := convertSliceOfJSONSchemaProps(&in.AllOf, &out.AllOf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := convertSliceOfJSONSchemaProps(&in.OneOf, &out.OneOf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := convertSliceOfJSONSchemaProps(&in.AnyOf, &out.AnyOf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.Not != nil {
|
|
||||||
in, out := &in.Not, &out.Not
|
|
||||||
*out = new(spec.Schema)
|
|
||||||
if err := ConvertJSONSchemaProps(*in, *out); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
out.Properties, err = convertMapOfJSONSchemaProps(in.Properties)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
out.PatternProperties, err = convertMapOfJSONSchemaProps(in.PatternProperties)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
out.Definitions, err = convertMapOfJSONSchemaProps(in.Definitions)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.Ref != nil {
|
|
||||||
out.Ref, err = spec.NewRef(*in.Ref)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.AdditionalProperties != nil {
|
|
||||||
in, out := &in.AdditionalProperties, &out.AdditionalProperties
|
|
||||||
*out = new(spec.SchemaOrBool)
|
|
||||||
if err := convertJSONSchemaPropsorBool(*in, *out); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.AdditionalItems != nil {
|
|
||||||
in, out := &in.AdditionalItems, &out.AdditionalItems
|
|
||||||
*out = new(spec.SchemaOrBool)
|
|
||||||
if err := convertJSONSchemaPropsorBool(*in, *out); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.Items != nil {
|
|
||||||
in, out := &in.Items, &out.Items
|
|
||||||
*out = new(spec.SchemaOrArray)
|
|
||||||
if err := convertJSONSchemaPropsOrArray(*in, *out); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.Dependencies != nil {
|
|
||||||
in, out := &in.Dependencies, &out.Dependencies
|
|
||||||
*out = make(spec.Dependencies, len(*in))
|
|
||||||
for key, val := range *in {
|
|
||||||
newVal := new(spec.SchemaOrStringArray)
|
|
||||||
if err := convertJSONSchemaPropsOrStringArray(&val, newVal); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
(*out)[key] = *newVal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.ExternalDocs != nil {
|
|
||||||
out.ExternalDocs = &spec.ExternalDocumentation{}
|
|
||||||
out.ExternalDocs.Description = in.ExternalDocs.Description
|
|
||||||
out.ExternalDocs.URL = in.ExternalDocs.URL
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertSliceOfJSONSchemaProps(in *[]apiextensions.JSONSchemaProps, out *[]spec.Schema) error {
|
|
||||||
if in != nil {
|
|
||||||
for _, jsonSchemaProps := range *in {
|
|
||||||
schema := spec.Schema{}
|
|
||||||
if err := ConvertJSONSchemaProps(&jsonSchemaProps, &schema); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*out = append(*out, schema)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertMapOfJSONSchemaProps(in map[string]apiextensions.JSONSchemaProps) (map[string]spec.Schema, error) {
|
|
||||||
out := make(map[string]spec.Schema)
|
|
||||||
if len(in) != 0 {
|
|
||||||
for k, jsonSchemaProps := range in {
|
|
||||||
schema := spec.Schema{}
|
|
||||||
if err := ConvertJSONSchemaProps(&jsonSchemaProps, &schema); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
out[k] = schema
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertJSONSchemaPropsOrArray(in *apiextensions.JSONSchemaPropsOrArray, out *spec.SchemaOrArray) error {
|
|
||||||
if in.Schema != nil {
|
|
||||||
in, out := &in.Schema, &out.Schema
|
|
||||||
*out = new(spec.Schema)
|
|
||||||
if err := ConvertJSONSchemaProps(*in, *out); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if in.JSONSchemas != nil {
|
|
||||||
in, out := &in.JSONSchemas, &out.Schemas
|
|
||||||
*out = make([]spec.Schema, len(*in))
|
|
||||||
for i := range *in {
|
|
||||||
if err := ConvertJSONSchemaProps(&(*in)[i], &(*out)[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertJSONSchemaPropsorBool(in *apiextensions.JSONSchemaPropsOrBool, out *spec.SchemaOrBool) error {
|
|
||||||
out.Allows = in.Allows
|
|
||||||
if in.Schema != nil {
|
|
||||||
in, out := &in.Schema, &out.Schema
|
|
||||||
*out = new(spec.Schema)
|
|
||||||
if err := ConvertJSONSchemaProps(*in, *out); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertJSONSchemaPropsOrStringArray(in *apiextensions.JSONSchemaPropsOrStringArray, out *spec.SchemaOrStringArray) error {
|
|
||||||
out.Property = in.Property
|
|
||||||
if in.Schema != nil {
|
|
||||||
in, out := &in.Schema, &out.Schema
|
|
||||||
*out = new(spec.Schema)
|
|
||||||
if err := ConvertJSONSchemaProps(*in, *out); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 validation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
|
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
|
||||||
"k8s.io/apimachinery/pkg/util/json"
|
|
||||||
|
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
|
||||||
apiextensionsfuzzer "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer"
|
|
||||||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestRoundTrip checks the conversion to go-openapi types.
|
|
||||||
// internal -> go-openapi -> JSON -> external -> internal
|
|
||||||
func TestRoundTrip(t *testing.T) {
|
|
||||||
scheme := runtime.NewScheme()
|
|
||||||
codecs := serializer.NewCodecFactory(scheme)
|
|
||||||
|
|
||||||
// add internal and external types to scheme
|
|
||||||
if err := apiextensions.AddToScheme(scheme); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := apiextensionsv1beta1.AddToScheme(scheme); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
seed := rand.Int63()
|
|
||||||
fuzzerFuncs := fuzzer.MergeFuzzerFuncs(apiextensionsfuzzer.Funcs)
|
|
||||||
f := fuzzer.FuzzerFor(fuzzerFuncs, rand.NewSource(seed), codecs)
|
|
||||||
|
|
||||||
for i := 0; i < 20; i++ {
|
|
||||||
// fuzz internal types
|
|
||||||
internal := &apiextensions.JSONSchemaProps{}
|
|
||||||
f.Fuzz(internal)
|
|
||||||
|
|
||||||
// internal -> go-openapi
|
|
||||||
openAPITypes := &spec.Schema{}
|
|
||||||
if err := ConvertJSONSchemaProps(internal, openAPITypes); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// go-openapi -> JSON
|
|
||||||
openAPIJSON, err := json.Marshal(openAPITypes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON -> external
|
|
||||||
external := &apiextensionsv1beta1.JSONSchemaProps{}
|
|
||||||
if err := json.Unmarshal(openAPIJSON, external); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// external -> internal
|
|
||||||
internalRoundTripped := &apiextensions.JSONSchemaProps{}
|
|
||||||
if err := scheme.Convert(external, internalRoundTripped, nil); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !apiequality.Semantic.DeepEqual(internal, internalRoundTripped) {
|
|
||||||
t.Fatalf("expected\n\t%#v, got \n\t%#v", internal, internalRoundTripped)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,8 +19,6 @@ package customresource
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/go-openapi/validate"
|
|
||||||
|
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
@ -49,7 +47,7 @@ type customResourceStrategy struct {
|
||||||
scale *apiextensions.CustomResourceSubresourceScale
|
scale *apiextensions.CustomResourceSubresourceScale
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.GroupVersionKind, schemaValidator, statusSchemaValidator *validate.SchemaValidator, status *apiextensions.CustomResourceSubresourceStatus, scale *apiextensions.CustomResourceSubresourceScale) customResourceStrategy {
|
func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.GroupVersionKind, status *apiextensions.CustomResourceSubresourceStatus, scale *apiextensions.CustomResourceSubresourceScale) customResourceStrategy {
|
||||||
return customResourceStrategy{
|
return customResourceStrategy{
|
||||||
ObjectTyper: typer,
|
ObjectTyper: typer,
|
||||||
NameGenerator: names.SimpleNameGenerator,
|
NameGenerator: names.SimpleNameGenerator,
|
||||||
|
@ -59,8 +57,6 @@ func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.Gr
|
||||||
validator: customResourceValidator{
|
validator: customResourceValidator{
|
||||||
namespaceScoped: namespaceScoped,
|
namespaceScoped: namespaceScoped,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
schemaValidator: schemaValidator,
|
|
||||||
statusSchemaValidator: statusSchemaValidator,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,6 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-openapi/validate"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
"k8s.io/apimachinery/pkg/api/validation"
|
"k8s.io/apimachinery/pkg/api/validation"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
@ -32,14 +30,11 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||||
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type customResourceValidator struct {
|
type customResourceValidator struct {
|
||||||
namespaceScoped bool
|
namespaceScoped bool
|
||||||
kind schema.GroupVersionKind
|
kind schema.GroupVersionKind
|
||||||
schemaValidator *validate.SchemaValidator
|
|
||||||
statusSchemaValidator *validate.SchemaValidator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a customResourceValidator) Validate(ctx context.Context, obj runtime.Object, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
|
func (a customResourceValidator) Validate(ctx context.Context, obj runtime.Object, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
|
||||||
|
@ -59,9 +54,6 @@ func (a customResourceValidator) Validate(ctx context.Context, obj runtime.Objec
|
||||||
var allErrs field.ErrorList
|
var allErrs field.ErrorList
|
||||||
|
|
||||||
allErrs = append(allErrs, validation.ValidateObjectMetaAccessor(accessor, a.namespaceScoped, validation.NameIsDNSSubdomain, field.NewPath("metadata"))...)
|
allErrs = append(allErrs, validation.ValidateObjectMetaAccessor(accessor, a.namespaceScoped, validation.NameIsDNSSubdomain, field.NewPath("metadata"))...)
|
||||||
if err = apiservervalidation.ValidateCustomResource(u.UnstructuredContent(), a.schemaValidator); err != nil {
|
|
||||||
allErrs = append(allErrs, field.Invalid(field.NewPath(""), u.UnstructuredContent(), err.Error()))
|
|
||||||
}
|
|
||||||
allErrs = append(allErrs, a.ValidateScaleSpec(ctx, u, scale)...)
|
allErrs = append(allErrs, a.ValidateScaleSpec(ctx, u, scale)...)
|
||||||
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
|
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
|
||||||
|
|
||||||
|
@ -89,9 +81,6 @@ func (a customResourceValidator) ValidateUpdate(ctx context.Context, obj, old ru
|
||||||
var allErrs field.ErrorList
|
var allErrs field.ErrorList
|
||||||
|
|
||||||
allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata"))...)
|
allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata"))...)
|
||||||
if err = apiservervalidation.ValidateCustomResource(u.UnstructuredContent(), a.schemaValidator); err != nil {
|
|
||||||
allErrs = append(allErrs, field.Invalid(field.NewPath(""), u.UnstructuredContent(), err.Error()))
|
|
||||||
}
|
|
||||||
allErrs = append(allErrs, a.ValidateScaleSpec(ctx, u, scale)...)
|
allErrs = append(allErrs, a.ValidateScaleSpec(ctx, u, scale)...)
|
||||||
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
|
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
|
||||||
|
|
||||||
|
@ -119,9 +108,6 @@ func (a customResourceValidator) ValidateStatusUpdate(ctx context.Context, obj,
|
||||||
var allErrs field.ErrorList
|
var allErrs field.ErrorList
|
||||||
|
|
||||||
allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata"))...)
|
allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata"))...)
|
||||||
if err = apiservervalidation.ValidateCustomResource(u.UnstructuredContent(), a.schemaValidator); err != nil {
|
|
||||||
allErrs = append(allErrs, field.Invalid(field.NewPath(""), u.UnstructuredContent(), err.Error()))
|
|
||||||
}
|
|
||||||
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
|
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
|
||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
|
|
|
@ -23,7 +23,6 @@ import (
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/mergepatch"
|
"k8s.io/apimachinery/pkg/util/mergepatch"
|
||||||
forkedjson "k8s.io/apimachinery/third_party/forked/golang/json"
|
forkedjson "k8s.io/apimachinery/third_party/forked/golang/json"
|
||||||
openapi "k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PatchMeta struct {
|
type PatchMeta struct {
|
||||||
|
@ -147,48 +146,3 @@ func GetTagStructTypeOrDie(dataStruct interface{}) reflect.Type {
|
||||||
}
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
type PatchMetaFromOpenAPI struct {
|
|
||||||
Schema openapi.Schema
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPatchMetaFromOpenAPI(s openapi.Schema) PatchMetaFromOpenAPI {
|
|
||||||
return PatchMetaFromOpenAPI{Schema: s}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ LookupPatchMeta = PatchMetaFromOpenAPI{}
|
|
||||||
|
|
||||||
func (s PatchMetaFromOpenAPI) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) {
|
|
||||||
if s.Schema == nil {
|
|
||||||
return nil, PatchMeta{}, nil
|
|
||||||
}
|
|
||||||
kindItem := NewKindItem(key, s.Schema.GetPath())
|
|
||||||
s.Schema.Accept(kindItem)
|
|
||||||
|
|
||||||
err := kindItem.Error()
|
|
||||||
if err != nil {
|
|
||||||
return nil, PatchMeta{}, err
|
|
||||||
}
|
|
||||||
return PatchMetaFromOpenAPI{Schema: kindItem.subschema},
|
|
||||||
kindItem.patchmeta, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s PatchMetaFromOpenAPI) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) {
|
|
||||||
if s.Schema == nil {
|
|
||||||
return nil, PatchMeta{}, nil
|
|
||||||
}
|
|
||||||
sliceItem := NewSliceItem(key, s.Schema.GetPath())
|
|
||||||
s.Schema.Accept(sliceItem)
|
|
||||||
|
|
||||||
err := sliceItem.Error()
|
|
||||||
if err != nil {
|
|
||||||
return nil, PatchMeta{}, err
|
|
||||||
}
|
|
||||||
return PatchMetaFromOpenAPI{Schema: sliceItem.subschema},
|
|
||||||
sliceItem.patchmeta, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s PatchMetaFromOpenAPI) Name() string {
|
|
||||||
schema := s.Schema
|
|
||||||
return schema.GetName()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,193 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 strategicpatch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/mergepatch"
|
|
||||||
openapi "k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
patchStrategyOpenapiextensionKey = "x-kubernetes-patch-strategy"
|
|
||||||
patchMergeKeyOpenapiextensionKey = "x-kubernetes-patch-merge-key"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LookupPatchItem interface {
|
|
||||||
openapi.SchemaVisitor
|
|
||||||
|
|
||||||
Error() error
|
|
||||||
Path() *openapi.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
type kindItem struct {
|
|
||||||
key string
|
|
||||||
path *openapi.Path
|
|
||||||
err error
|
|
||||||
patchmeta PatchMeta
|
|
||||||
subschema openapi.Schema
|
|
||||||
hasVisitKind bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewKindItem(key string, path *openapi.Path) *kindItem {
|
|
||||||
return &kindItem{
|
|
||||||
key: key,
|
|
||||||
path: path,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ LookupPatchItem = &kindItem{}
|
|
||||||
|
|
||||||
func (item *kindItem) Error() error {
|
|
||||||
return item.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item *kindItem) Path() *openapi.Path {
|
|
||||||
return item.path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item *kindItem) VisitPrimitive(schema *openapi.Primitive) {
|
|
||||||
item.err = errors.New("expected kind, but got primitive")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item *kindItem) VisitArray(schema *openapi.Array) {
|
|
||||||
item.err = errors.New("expected kind, but got slice")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item *kindItem) VisitMap(schema *openapi.Map) {
|
|
||||||
item.err = errors.New("expected kind, but got map")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item *kindItem) VisitReference(schema openapi.Reference) {
|
|
||||||
if !item.hasVisitKind {
|
|
||||||
schema.SubSchema().Accept(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item *kindItem) VisitKind(schema *openapi.Kind) {
|
|
||||||
subschema, ok := schema.Fields[item.key]
|
|
||||||
if !ok {
|
|
||||||
item.err = FieldNotFoundError{Path: schema.GetPath().String(), Field: item.key}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeKey, patchStrategies, err := parsePatchMetadata(subschema.GetExtensions())
|
|
||||||
if err != nil {
|
|
||||||
item.err = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
item.patchmeta = PatchMeta{
|
|
||||||
patchStrategies: patchStrategies,
|
|
||||||
patchMergeKey: mergeKey,
|
|
||||||
}
|
|
||||||
item.subschema = subschema
|
|
||||||
}
|
|
||||||
|
|
||||||
type sliceItem struct {
|
|
||||||
key string
|
|
||||||
path *openapi.Path
|
|
||||||
err error
|
|
||||||
patchmeta PatchMeta
|
|
||||||
subschema openapi.Schema
|
|
||||||
hasVisitKind bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSliceItem(key string, path *openapi.Path) *sliceItem {
|
|
||||||
return &sliceItem{
|
|
||||||
key: key,
|
|
||||||
path: path,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ LookupPatchItem = &sliceItem{}
|
|
||||||
|
|
||||||
func (item *sliceItem) Error() error {
|
|
||||||
return item.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item *sliceItem) Path() *openapi.Path {
|
|
||||||
return item.path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item *sliceItem) VisitPrimitive(schema *openapi.Primitive) {
|
|
||||||
item.err = errors.New("expected slice, but got primitive")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item *sliceItem) VisitArray(schema *openapi.Array) {
|
|
||||||
if !item.hasVisitKind {
|
|
||||||
item.err = errors.New("expected visit kind first, then visit array")
|
|
||||||
}
|
|
||||||
subschema := schema.SubType
|
|
||||||
item.subschema = subschema
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item *sliceItem) VisitMap(schema *openapi.Map) {
|
|
||||||
item.err = errors.New("expected slice, but got map")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item *sliceItem) VisitReference(schema openapi.Reference) {
|
|
||||||
if !item.hasVisitKind {
|
|
||||||
schema.SubSchema().Accept(item)
|
|
||||||
} else {
|
|
||||||
item.subschema = schema.SubSchema()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item *sliceItem) VisitKind(schema *openapi.Kind) {
|
|
||||||
subschema, ok := schema.Fields[item.key]
|
|
||||||
if !ok {
|
|
||||||
item.err = FieldNotFoundError{Path: schema.GetPath().String(), Field: item.key}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeKey, patchStrategies, err := parsePatchMetadata(subschema.GetExtensions())
|
|
||||||
if err != nil {
|
|
||||||
item.err = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
item.patchmeta = PatchMeta{
|
|
||||||
patchStrategies: patchStrategies,
|
|
||||||
patchMergeKey: mergeKey,
|
|
||||||
}
|
|
||||||
item.hasVisitKind = true
|
|
||||||
subschema.Accept(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsePatchMetadata(extensions map[string]interface{}) (string, []string, error) {
|
|
||||||
ps, foundPS := extensions[patchStrategyOpenapiextensionKey]
|
|
||||||
var patchStrategies []string
|
|
||||||
var mergeKey, patchStrategy string
|
|
||||||
var ok bool
|
|
||||||
if foundPS {
|
|
||||||
patchStrategy, ok = ps.(string)
|
|
||||||
if ok {
|
|
||||||
patchStrategies = strings.Split(patchStrategy, ",")
|
|
||||||
} else {
|
|
||||||
return "", nil, mergepatch.ErrBadArgType(patchStrategy, ps)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mk, foundMK := extensions[patchMergeKeyOpenapiextensionKey]
|
|
||||||
if foundMK {
|
|
||||||
mergeKey, ok = mk.(string)
|
|
||||||
if !ok {
|
|
||||||
return "", nil, mergepatch.ErrBadArgType(mergeKey, mk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mergeKey, patchStrategies, nil
|
|
||||||
}
|
|
|
@ -21,8 +21,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
"k8s.io/apiserver/pkg/authentication/group"
|
"k8s.io/apiserver/pkg/authentication/group"
|
||||||
"k8s.io/apiserver/pkg/authentication/request/anonymous"
|
"k8s.io/apiserver/pkg/authentication/request/anonymous"
|
||||||
|
@ -56,9 +54,8 @@ type DelegatingAuthenticatorConfig struct {
|
||||||
RequestHeaderConfig *RequestHeaderConfig
|
RequestHeaderConfig *RequestHeaderConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDefinitions, error) {
|
func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, error) {
|
||||||
authenticators := []authenticator.Request{}
|
authenticators := []authenticator.Request{}
|
||||||
securityDefinitions := spec.SecurityDefinitions{}
|
|
||||||
|
|
||||||
// front-proxy first, then remote
|
// front-proxy first, then remote
|
||||||
// Add the front proxy authenticator if requested
|
// Add the front proxy authenticator if requested
|
||||||
|
@ -71,7 +68,7 @@ func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.Secur
|
||||||
c.RequestHeaderConfig.ExtraHeaderPrefixes,
|
c.RequestHeaderConfig.ExtraHeaderPrefixes,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
authenticators = append(authenticators, requestHeaderAuthenticator)
|
authenticators = append(authenticators, requestHeaderAuthenticator)
|
||||||
}
|
}
|
||||||
|
@ -80,7 +77,7 @@ func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.Secur
|
||||||
if len(c.ClientCAFile) > 0 {
|
if len(c.ClientCAFile) > 0 {
|
||||||
clientCAs, err := cert.NewPool(c.ClientCAFile)
|
clientCAs, err := cert.NewPool(c.ClientCAFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("unable to load client CA file %s: %v", c.ClientCAFile, err)
|
return nil, fmt.Errorf("unable to load client CA file %s: %v", c.ClientCAFile, err)
|
||||||
}
|
}
|
||||||
verifyOpts := x509.DefaultVerifyOptions()
|
verifyOpts := x509.DefaultVerifyOptions()
|
||||||
verifyOpts.Roots = clientCAs
|
verifyOpts.Roots = clientCAs
|
||||||
|
@ -90,31 +87,22 @@ func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.Secur
|
||||||
if c.TokenAccessReviewClient != nil {
|
if c.TokenAccessReviewClient != nil {
|
||||||
tokenAuth, err := webhooktoken.NewFromInterface(c.TokenAccessReviewClient, c.APIAudiences)
|
tokenAuth, err := webhooktoken.NewFromInterface(c.TokenAccessReviewClient, c.APIAudiences)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cachingTokenAuth := cache.New(tokenAuth, false, c.CacheTTL, c.CacheTTL)
|
cachingTokenAuth := cache.New(tokenAuth, false, c.CacheTTL, c.CacheTTL)
|
||||||
authenticators = append(authenticators, bearertoken.New(cachingTokenAuth), websocket.NewProtocolAuthenticator(cachingTokenAuth))
|
authenticators = append(authenticators, bearertoken.New(cachingTokenAuth), websocket.NewProtocolAuthenticator(cachingTokenAuth))
|
||||||
|
|
||||||
securityDefinitions["BearerToken"] = &spec.SecurityScheme{
|
|
||||||
SecuritySchemeProps: spec.SecuritySchemeProps{
|
|
||||||
Type: "apiKey",
|
|
||||||
Name: "authorization",
|
|
||||||
In: "header",
|
|
||||||
Description: "Bearer Token authentication",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(authenticators) == 0 {
|
if len(authenticators) == 0 {
|
||||||
if c.Anonymous {
|
if c.Anonymous {
|
||||||
return anonymous.NewAuthenticator(), &securityDefinitions, nil
|
return anonymous.NewAuthenticator(), nil
|
||||||
}
|
}
|
||||||
return nil, nil, errors.New("No authentication method configured")
|
return nil, errors.New("No authentication method configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticator := group.NewAuthenticatedGroupAdder(unionauth.New(authenticators...))
|
authenticator := group.NewAuthenticatedGroupAdder(unionauth.New(authenticators...))
|
||||||
if c.Anonymous {
|
if c.Anonymous {
|
||||||
authenticator = unionauth.NewFailOnError(authenticator, anonymous.NewAuthenticator())
|
authenticator = unionauth.NewFailOnError(authenticator, anonymous.NewAuthenticator())
|
||||||
}
|
}
|
||||||
return authenticator, &securityDefinitions, nil
|
return authenticator, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@ import (
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/apiserver/pkg/endpoints/discovery"
|
"k8s.io/apiserver/pkg/endpoints/discovery"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIGroupVersion is a helper for exposing rest.Storage objects as http.Handlers via go-restful
|
// APIGroupVersion is a helper for exposing rest.Storage objects as http.Handlers via go-restful
|
||||||
|
@ -80,9 +79,6 @@ type APIGroupVersion struct {
|
||||||
Admit admission.Interface
|
Admit admission.Interface
|
||||||
|
|
||||||
MinRequestTimeout time.Duration
|
MinRequestTimeout time.Duration
|
||||||
|
|
||||||
// OpenAPIModels exposes the OpenAPI models to each individual handler.
|
|
||||||
OpenAPIModels openapiproto.Models
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
|
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
|
||||||
|
|
|
@ -42,7 +42,6 @@ import (
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
utiltrace "k8s.io/apiserver/pkg/util/trace"
|
utiltrace "k8s.io/apiserver/pkg/util/trace"
|
||||||
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RequestScope encapsulates common fields across all RESTful handler methods.
|
// RequestScope encapsulates common fields across all RESTful handler methods.
|
||||||
|
@ -61,7 +60,6 @@ type RequestScope struct {
|
||||||
Trace *utiltrace.Trace
|
Trace *utiltrace.Trace
|
||||||
|
|
||||||
TableConvertor rest.TableConvertor
|
TableConvertor rest.TableConvertor
|
||||||
OpenAPIModels openapiproto.Models
|
|
||||||
|
|
||||||
Resource schema.GroupVersionResource
|
Resource schema.GroupVersionResource
|
||||||
Kind schema.GroupVersionKind
|
Kind schema.GroupVersionKind
|
||||||
|
|
|
@ -507,7 +507,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||||
if a.group.MetaGroupVersion != nil {
|
if a.group.MetaGroupVersion != nil {
|
||||||
reqScope.MetaGroupVersion = *a.group.MetaGroupVersion
|
reqScope.MetaGroupVersion = *a.group.MetaGroupVersion
|
||||||
}
|
}
|
||||||
reqScope.OpenAPIModels = a.group.OpenAPIModels
|
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
producedObject := storageMeta.ProducesObject(action.Verb)
|
producedObject := storageMeta.ProducesObject(action.Verb)
|
||||||
if producedObject == nil {
|
if producedObject == nil {
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
"go_test",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = ["openapi_test.go"],
|
|
||||||
embed = [":go_default_library"],
|
|
||||||
deps = [
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apiserver/pkg/endpoints/openapi/testing:go_default_library",
|
|
||||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["openapi.go"],
|
|
||||||
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/openapi",
|
|
||||||
importpath = "k8s.io/apiserver/pkg/endpoints/openapi",
|
|
||||||
deps = [
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
|
||||||
"//vendor/github.com/emicklei/go-restful:go_default_library",
|
|
||||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/util:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [
|
|
||||||
":package-srcs",
|
|
||||||
"//staging/src/k8s.io/apiserver/pkg/endpoints/openapi/testing:all-srcs",
|
|
||||||
],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
|
@ -1,2 +0,0 @@
|
||||||
reviewers:
|
|
||||||
- mbohlool
|
|
|
@ -1,179 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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 openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
|
|
||||||
restful "github.com/emicklei/go-restful"
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/kube-openapi/pkg/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
var verbs = util.NewTrie([]string{"get", "log", "read", "replace", "patch", "delete", "deletecollection", "watch", "connect", "proxy", "list", "create", "patch"})
|
|
||||||
|
|
||||||
const (
|
|
||||||
extensionGVK = "x-kubernetes-group-version-kind"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ToValidOperationID makes an string a valid op ID (e.g. removing punctuations and whitespaces and make it camel case)
|
|
||||||
func ToValidOperationID(s string, capitalizeFirstLetter bool) string {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
capitalize := capitalizeFirstLetter
|
|
||||||
for i, r := range s {
|
|
||||||
if unicode.IsLetter(r) || r == '_' || (i != 0 && unicode.IsDigit(r)) {
|
|
||||||
if capitalize {
|
|
||||||
buffer.WriteRune(unicode.ToUpper(r))
|
|
||||||
capitalize = false
|
|
||||||
} else {
|
|
||||||
buffer.WriteRune(r)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
capitalize = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOperationIDAndTags returns a customize operation ID and a list of tags for kubernetes API server's OpenAPI spec to prevent duplicate IDs.
|
|
||||||
func GetOperationIDAndTags(r *restful.Route) (string, []string, error) {
|
|
||||||
op := r.Operation
|
|
||||||
path := r.Path
|
|
||||||
var tags []string
|
|
||||||
prefix, exists := verbs.GetPrefix(op)
|
|
||||||
if !exists {
|
|
||||||
return op, tags, fmt.Errorf("operation names should start with a verb. Cannot determine operation verb from %v", op)
|
|
||||||
}
|
|
||||||
op = op[len(prefix):]
|
|
||||||
parts := strings.Split(strings.Trim(path, "/"), "/")
|
|
||||||
// Assume /api is /apis/core, remove this when we actually server /api/... on /apis/core/...
|
|
||||||
if len(parts) >= 1 && parts[0] == "api" {
|
|
||||||
parts = append([]string{"apis", "core"}, parts[1:]...)
|
|
||||||
}
|
|
||||||
if len(parts) >= 2 && parts[0] == "apis" {
|
|
||||||
trimmed := strings.TrimSuffix(parts[1], ".k8s.io")
|
|
||||||
prefix = prefix + ToValidOperationID(trimmed, prefix != "")
|
|
||||||
tag := ToValidOperationID(trimmed, false)
|
|
||||||
if len(parts) > 2 {
|
|
||||||
prefix = prefix + ToValidOperationID(parts[2], prefix != "")
|
|
||||||
tag = tag + "_" + ToValidOperationID(parts[2], false)
|
|
||||||
}
|
|
||||||
tags = append(tags, tag)
|
|
||||||
} else if len(parts) >= 1 {
|
|
||||||
tags = append(tags, ToValidOperationID(parts[0], false))
|
|
||||||
}
|
|
||||||
return prefix + ToValidOperationID(op, prefix != ""), tags, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type groupVersionKinds []v1.GroupVersionKind
|
|
||||||
|
|
||||||
func (s groupVersionKinds) Len() int {
|
|
||||||
return len(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s groupVersionKinds) Swap(i, j int) {
|
|
||||||
s[i], s[j] = s[j], s[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s groupVersionKinds) Less(i, j int) bool {
|
|
||||||
if s[i].Group == s[j].Group {
|
|
||||||
if s[i].Version == s[j].Version {
|
|
||||||
return s[i].Kind < s[j].Kind
|
|
||||||
}
|
|
||||||
return s[i].Version < s[j].Version
|
|
||||||
}
|
|
||||||
return s[i].Group < s[j].Group
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefinitionNamer is the type to customize OpenAPI definition name.
|
|
||||||
type DefinitionNamer struct {
|
|
||||||
typeGroupVersionKinds map[string]groupVersionKinds
|
|
||||||
}
|
|
||||||
|
|
||||||
func gvkConvert(gvk schema.GroupVersionKind) v1.GroupVersionKind {
|
|
||||||
return v1.GroupVersionKind{
|
|
||||||
Group: gvk.Group,
|
|
||||||
Version: gvk.Version,
|
|
||||||
Kind: gvk.Kind,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func friendlyName(name string) string {
|
|
||||||
nameParts := strings.Split(name, "/")
|
|
||||||
// Reverse first part. e.g., io.k8s... instead of k8s.io...
|
|
||||||
if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") {
|
|
||||||
parts := strings.Split(nameParts[0], ".")
|
|
||||||
for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 {
|
|
||||||
parts[i], parts[j] = parts[j], parts[i]
|
|
||||||
}
|
|
||||||
nameParts[0] = strings.Join(parts, ".")
|
|
||||||
}
|
|
||||||
return strings.Join(nameParts, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func typeName(t reflect.Type) string {
|
|
||||||
path := t.PkgPath()
|
|
||||||
if strings.Contains(path, "/vendor/") {
|
|
||||||
path = path[strings.Index(path, "/vendor/")+len("/vendor/"):]
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s.%s", path, t.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefinitionNamer constructs a new DefinitionNamer to be used to customize OpenAPI spec.
|
|
||||||
func NewDefinitionNamer(schemes ...*runtime.Scheme) *DefinitionNamer {
|
|
||||||
ret := &DefinitionNamer{
|
|
||||||
typeGroupVersionKinds: map[string]groupVersionKinds{},
|
|
||||||
}
|
|
||||||
for _, s := range schemes {
|
|
||||||
for gvk, rtype := range s.AllKnownTypes() {
|
|
||||||
newGVK := gvkConvert(gvk)
|
|
||||||
exists := false
|
|
||||||
for _, existingGVK := range ret.typeGroupVersionKinds[typeName(rtype)] {
|
|
||||||
if newGVK == existingGVK {
|
|
||||||
exists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
ret.typeGroupVersionKinds[typeName(rtype)] = append(ret.typeGroupVersionKinds[typeName(rtype)], newGVK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, gvk := range ret.typeGroupVersionKinds {
|
|
||||||
sort.Sort(gvk)
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDefinitionName returns the name and tags for a given definition
|
|
||||||
func (d *DefinitionNamer) GetDefinitionName(name string) (string, spec.Extensions) {
|
|
||||||
if groupVersionKinds, ok := d.typeGroupVersionKinds[name]; ok {
|
|
||||||
return friendlyName(name), spec.Extensions{
|
|
||||||
extensionGVK: []v1.GroupVersionKind(groupVersionKinds),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return friendlyName(name), nil
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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 openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
openapitesting "k8s.io/apiserver/pkg/endpoints/openapi/testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func assertEqual(t *testing.T, expected, actual interface{}) {
|
|
||||||
var equal bool
|
|
||||||
if expected == nil || actual == nil {
|
|
||||||
equal = expected == actual
|
|
||||||
} else {
|
|
||||||
equal = reflect.DeepEqual(expected, actual)
|
|
||||||
}
|
|
||||||
if !equal {
|
|
||||||
t.Errorf("%v != %v", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetDefinitionName(t *testing.T) {
|
|
||||||
testType := openapitesting.TestType{}
|
|
||||||
// in production, the name is stripped of ".*vendor/" prefix before passed
|
|
||||||
// to GetDefinitionName, so here typePkgName does not have the
|
|
||||||
// "k8s.io/kubernetes/vendor" prefix.
|
|
||||||
typePkgName := "k8s.io/apiserver/pkg/endpoints/openapi/testing.TestType"
|
|
||||||
typeFriendlyName := "io.k8s.apiserver.pkg.endpoints.openapi.testing.TestType"
|
|
||||||
if strings.HasSuffix(reflect.TypeOf(testType).PkgPath(), "go_default_test") {
|
|
||||||
// the test is running inside bazel where the package name is changed and
|
|
||||||
// "go_default_test" will add to package path.
|
|
||||||
typePkgName = "k8s.io/apiserver/pkg/endpoints/openapi/testing/go_default_test.TestType"
|
|
||||||
typeFriendlyName = "io.k8s.apiserver.pkg.endpoints.openapi.testing.go_default_test.TestType"
|
|
||||||
}
|
|
||||||
s := runtime.NewScheme()
|
|
||||||
s.AddKnownTypeWithName(testType.GroupVersionKind(), &testType)
|
|
||||||
namer := NewDefinitionNamer(s)
|
|
||||||
n, e := namer.GetDefinitionName(typePkgName)
|
|
||||||
assertEqual(t, typeFriendlyName, n)
|
|
||||||
assertEqual(t, e["x-kubernetes-group-version-kind"], []v1.GroupVersionKind{
|
|
||||||
{
|
|
||||||
Group: "test",
|
|
||||||
Version: "v1",
|
|
||||||
Kind: "TestType",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
n, e2 := namer.GetDefinitionName("test.com/another.Type")
|
|
||||||
assertEqual(t, "com.test.another.Type", n)
|
|
||||||
assertEqual(t, e2, spec.Extensions(nil))
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = [
|
|
||||||
"types.go",
|
|
||||||
"zz_generated.deepcopy.go",
|
|
||||||
],
|
|
||||||
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/openapi/testing",
|
|
||||||
importpath = "k8s.io/apiserver/pkg/endpoints/openapi/testing",
|
|
||||||
deps = [
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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 testing
|
|
||||||
|
|
||||||
import (
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
|
||||||
type TestType struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t TestType) GetObjectKind() schema.ObjectKind {
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t TestType) SetGroupVersionKind(kind schema.GroupVersionKind) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t TestType) GroupVersionKind() schema.GroupVersionKind {
|
|
||||||
return schema.GroupVersionKind{
|
|
||||||
Group: "test",
|
|
||||||
Version: "v1",
|
|
||||||
Kind: "TestType",
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
// +build !ignore_autogenerated
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
|
||||||
|
|
||||||
package testing
|
|
||||||
|
|
||||||
import (
|
|
||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *TestType) DeepCopyInto(out *TestType) {
|
|
||||||
*out = *in
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestType.
|
|
||||||
func (in *TestType) DeepCopy() *TestType {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(TestType)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
|
||||||
func (in *TestType) DeepCopyObject() runtime.Object {
|
|
||||||
if c := in.DeepCopy(); c != nil {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -23,13 +23,10 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
goruntime "runtime"
|
goruntime "runtime"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/emicklei/go-restful-swagger12"
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
"github.com/pborman/uuid"
|
"github.com/pborman/uuid"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
|
|
||||||
|
@ -50,7 +47,6 @@ import (
|
||||||
authorizerunion "k8s.io/apiserver/pkg/authorization/union"
|
authorizerunion "k8s.io/apiserver/pkg/authorization/union"
|
||||||
"k8s.io/apiserver/pkg/endpoints/discovery"
|
"k8s.io/apiserver/pkg/endpoints/discovery"
|
||||||
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
|
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
|
||||||
apiopenapi "k8s.io/apiserver/pkg/endpoints/openapi"
|
|
||||||
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
genericregistry "k8s.io/apiserver/pkg/registry/generic"
|
genericregistry "k8s.io/apiserver/pkg/registry/generic"
|
||||||
genericfilters "k8s.io/apiserver/pkg/server/filters"
|
genericfilters "k8s.io/apiserver/pkg/server/filters"
|
||||||
|
@ -61,7 +57,6 @@ import (
|
||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
certutil "k8s.io/client-go/util/cert"
|
certutil "k8s.io/client-go/util/cert"
|
||||||
openapicommon "k8s.io/kube-openapi/pkg/common"
|
|
||||||
|
|
||||||
// install apis
|
// install apis
|
||||||
_ "k8s.io/apiserver/pkg/apis/apiserver/install"
|
_ "k8s.io/apiserver/pkg/apis/apiserver/install"
|
||||||
|
@ -99,7 +94,6 @@ type Config struct {
|
||||||
AdmissionControl admission.Interface
|
AdmissionControl admission.Interface
|
||||||
CorsAllowedOriginList []string
|
CorsAllowedOriginList []string
|
||||||
|
|
||||||
EnableSwaggerUI bool
|
|
||||||
EnableIndex bool
|
EnableIndex bool
|
||||||
EnableProfiling bool
|
EnableProfiling bool
|
||||||
EnableDiscovery bool
|
EnableDiscovery bool
|
||||||
|
@ -141,10 +135,6 @@ type Config struct {
|
||||||
// Serializer is required and provides the interface for serializing and converting objects to and from the wire
|
// Serializer is required and provides the interface for serializing and converting objects to and from the wire
|
||||||
// The default (api.Codecs) usually works fine.
|
// The default (api.Codecs) usually works fine.
|
||||||
Serializer runtime.NegotiatedSerializer
|
Serializer runtime.NegotiatedSerializer
|
||||||
// OpenAPIConfig will be used in generating OpenAPI spec. This is nil by default. Use DefaultOpenAPIConfig for "working" defaults.
|
|
||||||
OpenAPIConfig *openapicommon.Config
|
|
||||||
// SwaggerConfig will be used in generating Swagger spec. This is nil by default. Use DefaultSwaggerConfig for "working" defaults.
|
|
||||||
SwaggerConfig *swagger.Config
|
|
||||||
|
|
||||||
// RESTOptionsGetter is used to construct RESTStorage types via the generic registry.
|
// RESTOptionsGetter is used to construct RESTStorage types via the generic registry.
|
||||||
RESTOptionsGetter genericregistry.RESTOptionsGetter
|
RESTOptionsGetter genericregistry.RESTOptionsGetter
|
||||||
|
@ -269,43 +259,6 @@ func NewRecommendedConfig(codecs serializer.CodecFactory) *RecommendedConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultOpenAPIConfig(getDefinitions openapicommon.GetOpenAPIDefinitions, defNamer *apiopenapi.DefinitionNamer) *openapicommon.Config {
|
|
||||||
return &openapicommon.Config{
|
|
||||||
ProtocolList: []string{"https"},
|
|
||||||
IgnorePrefixes: []string{"/swaggerapi"},
|
|
||||||
Info: &spec.Info{
|
|
||||||
InfoProps: spec.InfoProps{
|
|
||||||
Title: "Generic API Server",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
DefaultResponse: &spec.Response{
|
|
||||||
ResponseProps: spec.ResponseProps{
|
|
||||||
Description: "Default Response.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
GetOperationIDAndTags: apiopenapi.GetOperationIDAndTags,
|
|
||||||
GetDefinitionName: defNamer.GetDefinitionName,
|
|
||||||
GetDefinitions: getDefinitions,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultSwaggerConfig returns a default configuration without WebServiceURL and
|
|
||||||
// WebServices set.
|
|
||||||
func DefaultSwaggerConfig() *swagger.Config {
|
|
||||||
return &swagger.Config{
|
|
||||||
ApiPath: "/swaggerapi",
|
|
||||||
SwaggerPath: "/swaggerui/",
|
|
||||||
SwaggerFilePath: "/swagger-ui/",
|
|
||||||
SchemaFormatHandler: func(typeName string) string {
|
|
||||||
switch typeName {
|
|
||||||
case "metav1.Time", "*metav1.Time":
|
|
||||||
return "date-time"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *AuthenticationInfo) ApplyClientCert(clientCAFile string, servingInfo *SecureServingInfo) error {
|
func (c *AuthenticationInfo) ApplyClientCert(clientCAFile string, servingInfo *SecureServingInfo) error {
|
||||||
if servingInfo != nil {
|
if servingInfo != nil {
|
||||||
if len(clientCAFile) > 0 {
|
if len(clientCAFile) > 0 {
|
||||||
|
@ -360,49 +313,6 @@ func (c *Config) Complete(informers informers.SharedInformerFactory) CompletedCo
|
||||||
c.ExternalAddress = net.JoinHostPort(c.ExternalAddress, strconv.Itoa(port))
|
c.ExternalAddress = net.JoinHostPort(c.ExternalAddress, strconv.Itoa(port))
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.OpenAPIConfig != nil {
|
|
||||||
if c.OpenAPIConfig.SecurityDefinitions != nil {
|
|
||||||
// Setup OpenAPI security: all APIs will have the same authentication for now.
|
|
||||||
c.OpenAPIConfig.DefaultSecurity = []map[string][]string{}
|
|
||||||
keys := []string{}
|
|
||||||
for k := range *c.OpenAPIConfig.SecurityDefinitions {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
for _, k := range keys {
|
|
||||||
c.OpenAPIConfig.DefaultSecurity = append(c.OpenAPIConfig.DefaultSecurity, map[string][]string{k: {}})
|
|
||||||
}
|
|
||||||
if c.OpenAPIConfig.CommonResponses == nil {
|
|
||||||
c.OpenAPIConfig.CommonResponses = map[int]spec.Response{}
|
|
||||||
}
|
|
||||||
if _, exists := c.OpenAPIConfig.CommonResponses[http.StatusUnauthorized]; !exists {
|
|
||||||
c.OpenAPIConfig.CommonResponses[http.StatusUnauthorized] = spec.Response{
|
|
||||||
ResponseProps: spec.ResponseProps{
|
|
||||||
Description: "Unauthorized",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure we populate info, and info.version, if not manually set
|
|
||||||
if c.OpenAPIConfig.Info == nil {
|
|
||||||
c.OpenAPIConfig.Info = &spec.Info{}
|
|
||||||
}
|
|
||||||
if c.OpenAPIConfig.Info.Version == "" {
|
|
||||||
if c.Version != nil {
|
|
||||||
c.OpenAPIConfig.Info.Version = strings.Split(c.Version.String(), "-")[0]
|
|
||||||
} else {
|
|
||||||
c.OpenAPIConfig.Info.Version = "unversioned"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.SwaggerConfig != nil && len(c.SwaggerConfig.WebServicesUrl) == 0 {
|
|
||||||
if c.SecureServing != nil {
|
|
||||||
c.SwaggerConfig.WebServicesUrl = "https://" + c.ExternalAddress
|
|
||||||
} else {
|
|
||||||
c.SwaggerConfig.WebServicesUrl = "http://" + c.ExternalAddress
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.DiscoveryAddresses == nil {
|
if c.DiscoveryAddresses == nil {
|
||||||
c.DiscoveryAddresses = discovery.DefaultAddresses{DefaultAddress: c.ExternalAddress}
|
c.DiscoveryAddresses = discovery.DefaultAddresses{DefaultAddress: c.ExternalAddress}
|
||||||
}
|
}
|
||||||
|
@ -459,9 +369,6 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G
|
||||||
|
|
||||||
listedPathProvider: apiServerHandler,
|
listedPathProvider: apiServerHandler,
|
||||||
|
|
||||||
swaggerConfig: c.SwaggerConfig,
|
|
||||||
openAPIConfig: c.OpenAPIConfig,
|
|
||||||
|
|
||||||
postStartHooks: map[string]postStartHookEntry{},
|
postStartHooks: map[string]postStartHookEntry{},
|
||||||
preShutdownHooks: map[string]preShutdownHookEntry{},
|
preShutdownHooks: map[string]preShutdownHookEntry{},
|
||||||
disabledPostStartHooks: c.DisabledPostStartHooks,
|
disabledPostStartHooks: c.DisabledPostStartHooks,
|
||||||
|
@ -541,9 +448,6 @@ func installAPI(s *GenericAPIServer, c *Config) {
|
||||||
if c.EnableIndex {
|
if c.EnableIndex {
|
||||||
routes.Index{}.Install(s.listedPathProvider, s.Handler.NonGoRestfulMux)
|
routes.Index{}.Install(s.listedPathProvider, s.Handler.NonGoRestfulMux)
|
||||||
}
|
}
|
||||||
if c.SwaggerConfig != nil && c.EnableSwaggerUI {
|
|
||||||
routes.SwaggerUI{}.Install(s.Handler.NonGoRestfulMux)
|
|
||||||
}
|
|
||||||
if c.EnableProfiling {
|
if c.EnableProfiling {
|
||||||
routes.Profiling{}.Install(s.Handler.NonGoRestfulMux)
|
routes.Profiling{}.Install(s.Handler.NonGoRestfulMux)
|
||||||
if c.EnableContentionProfiling {
|
if c.EnableContentionProfiling {
|
||||||
|
|
|
@ -19,13 +19,11 @@ package server
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
gpath "path"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
systemd "github.com/coreos/go-systemd/daemon"
|
systemd "github.com/coreos/go-systemd/daemon"
|
||||||
"github.com/emicklei/go-restful-swagger12"
|
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
@ -43,12 +41,7 @@ import (
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
"k8s.io/apiserver/pkg/server/healthz"
|
"k8s.io/apiserver/pkg/server/healthz"
|
||||||
"k8s.io/apiserver/pkg/server/routes"
|
"k8s.io/apiserver/pkg/server/routes"
|
||||||
utilopenapi "k8s.io/apiserver/pkg/util/openapi"
|
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
openapibuilder "k8s.io/kube-openapi/pkg/builder"
|
|
||||||
openapicommon "k8s.io/kube-openapi/pkg/common"
|
|
||||||
openapiutil "k8s.io/kube-openapi/pkg/util"
|
|
||||||
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Info about an API group.
|
// Info about an API group.
|
||||||
|
@ -120,10 +113,6 @@ type GenericAPIServer struct {
|
||||||
// DiscoveryGroupManager serves /apis
|
// DiscoveryGroupManager serves /apis
|
||||||
DiscoveryGroupManager discovery.GroupManager
|
DiscoveryGroupManager discovery.GroupManager
|
||||||
|
|
||||||
// Enable swagger and/or OpenAPI if these configs are non-nil.
|
|
||||||
swaggerConfig *swagger.Config
|
|
||||||
openAPIConfig *openapicommon.Config
|
|
||||||
|
|
||||||
// PostStartHooks are each called after the server has started listening, in a separate go func for each
|
// PostStartHooks are each called after the server has started listening, in a separate go func for each
|
||||||
// with no guarantee of ordering between them. The map key is a name used for error reporting.
|
// with no guarantee of ordering between them. The map key is a name used for error reporting.
|
||||||
// It may kill the process with a panic if it wishes to by returning an error.
|
// It may kill the process with a panic if it wishes to by returning an error.
|
||||||
|
@ -232,15 +221,6 @@ type preparedGenericAPIServer struct {
|
||||||
|
|
||||||
// PrepareRun does post API installation setup steps.
|
// PrepareRun does post API installation setup steps.
|
||||||
func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer {
|
func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer {
|
||||||
if s.swaggerConfig != nil {
|
|
||||||
routes.Swagger{Config: s.swaggerConfig}.Install(s.Handler.GoRestfulContainer)
|
|
||||||
}
|
|
||||||
if s.openAPIConfig != nil {
|
|
||||||
routes.OpenAPI{
|
|
||||||
Config: s.openAPIConfig,
|
|
||||||
}.Install(s.Handler.GoRestfulContainer, s.Handler.NonGoRestfulMux)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.installHealthz()
|
s.installHealthz()
|
||||||
|
|
||||||
// Register audit backend preShutdownHook.
|
// Register audit backend preShutdownHook.
|
||||||
|
@ -321,10 +301,6 @@ func (s preparedGenericAPIServer) NonBlockingRun(stopCh <-chan struct{}) error {
|
||||||
|
|
||||||
// installAPIResources is a private method for installing the REST storage backing each api groupversionresource
|
// installAPIResources is a private method for installing the REST storage backing each api groupversionresource
|
||||||
func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo) error {
|
func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo) error {
|
||||||
openAPIGroupModels, err := s.getOpenAPIModelsForGroup(apiPrefix, apiGroupInfo)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to get openapi models for group %v: %v", apiPrefix, err)
|
|
||||||
}
|
|
||||||
for _, groupVersion := range apiGroupInfo.PrioritizedVersions {
|
for _, groupVersion := range apiGroupInfo.PrioritizedVersions {
|
||||||
if len(apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version]) == 0 {
|
if len(apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version]) == 0 {
|
||||||
klog.Warningf("Skipping API %v because it has no resources.", groupVersion)
|
klog.Warningf("Skipping API %v because it has no resources.", groupVersion)
|
||||||
|
@ -335,7 +311,6 @@ func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *A
|
||||||
if apiGroupInfo.OptionsExternalVersion != nil {
|
if apiGroupInfo.OptionsExternalVersion != nil {
|
||||||
apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion
|
apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion
|
||||||
}
|
}
|
||||||
apiGroupVersion.OpenAPIModels = openAPIGroupModels
|
|
||||||
|
|
||||||
if err := apiGroupVersion.InstallREST(s.Handler.GoRestfulContainer); err != nil {
|
if err := apiGroupVersion.InstallREST(s.Handler.GoRestfulContainer); err != nil {
|
||||||
return fmt.Errorf("unable to setup API %v: %v", apiGroupInfo, err)
|
return fmt.Errorf("unable to setup API %v: %v", apiGroupInfo, err)
|
||||||
|
@ -449,37 +424,3 @@ func NewDefaultAPIGroupInfo(group string, scheme *runtime.Scheme, parameterCodec
|
||||||
NegotiatedSerializer: codecs,
|
NegotiatedSerializer: codecs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOpenAPIModelsForGroup is a private method for getting the OpenAPI Schemas for each api group
|
|
||||||
func (s *GenericAPIServer) getOpenAPIModelsForGroup(apiPrefix string, apiGroupInfo *APIGroupInfo) (openapiproto.Models, error) {
|
|
||||||
if s.openAPIConfig == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
pathsToIgnore := openapiutil.NewTrie(s.openAPIConfig.IgnorePrefixes)
|
|
||||||
// Get the canonical names of every resource we need to build in this api group
|
|
||||||
resourceNames := make([]string, 0)
|
|
||||||
for _, groupVersion := range apiGroupInfo.PrioritizedVersions {
|
|
||||||
for resource, storage := range apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version] {
|
|
||||||
path := gpath.Join(apiPrefix, groupVersion.Group, groupVersion.Version, resource)
|
|
||||||
if !pathsToIgnore.HasPrefix(path) {
|
|
||||||
kind, err := genericapi.GetResourceKind(groupVersion, storage, apiGroupInfo.Scheme)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sampleObject, err := apiGroupInfo.Scheme.New(kind)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
name := openapiutil.GetCanonicalTypeName(sampleObject)
|
|
||||||
resourceNames = append(resourceNames, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the openapi definitions for those resources and convert it to proto models
|
|
||||||
openAPISpec, err := openapibuilder.BuildOpenAPIDefinitionsForResources(s.openAPIConfig, resourceNames...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return utilopenapi.ToProtoModels(openAPISpec)
|
|
||||||
}
|
|
||||||
|
|
|
@ -33,7 +33,6 @@ import (
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
openapicommon "k8s.io/kube-openapi/pkg/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RequestHeaderAuthenticationOptions struct {
|
type RequestHeaderAuthenticationOptions struct {
|
||||||
|
@ -170,7 +169,7 @@ func (s *DelegatingAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
|
||||||
"Note that this can result in authentication that treats all requests as anonymous.")
|
"Note that this can result in authentication that treats all requests as anonymous.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DelegatingAuthenticationOptions) ApplyTo(c *server.AuthenticationInfo, servingInfo *server.SecureServingInfo, openAPIConfig *openapicommon.Config) error {
|
func (s *DelegatingAuthenticationOptions) ApplyTo(c *server.AuthenticationInfo, servingInfo *server.SecureServingInfo) error {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
c.Authenticator = nil
|
c.Authenticator = nil
|
||||||
return nil
|
return nil
|
||||||
|
@ -212,14 +211,11 @@ func (s *DelegatingAuthenticationOptions) ApplyTo(c *server.AuthenticationInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
// create authenticator
|
// create authenticator
|
||||||
authenticator, securityDefinitions, err := cfg.New()
|
authenticator, err := cfg.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.Authenticator = authenticator
|
c.Authenticator = authenticator
|
||||||
if openAPIConfig != nil {
|
|
||||||
openAPIConfig.SecurityDefinitions = securityDefinitions
|
|
||||||
}
|
|
||||||
c.SupportsBasicAuth = false
|
c.SupportsBasicAuth = false
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -26,7 +26,6 @@ import (
|
||||||
type FeatureOptions struct {
|
type FeatureOptions struct {
|
||||||
EnableProfiling bool
|
EnableProfiling bool
|
||||||
EnableContentionProfiling bool
|
EnableContentionProfiling bool
|
||||||
EnableSwaggerUI bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFeatureOptions() *FeatureOptions {
|
func NewFeatureOptions() *FeatureOptions {
|
||||||
|
@ -35,7 +34,6 @@ func NewFeatureOptions() *FeatureOptions {
|
||||||
return &FeatureOptions{
|
return &FeatureOptions{
|
||||||
EnableProfiling: defaults.EnableProfiling,
|
EnableProfiling: defaults.EnableProfiling,
|
||||||
EnableContentionProfiling: defaults.EnableContentionProfiling,
|
EnableContentionProfiling: defaults.EnableContentionProfiling,
|
||||||
EnableSwaggerUI: defaults.EnableSwaggerUI,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,8 +46,6 @@ func (o *FeatureOptions) AddFlags(fs *pflag.FlagSet) {
|
||||||
"Enable profiling via web interface host:port/debug/pprof/")
|
"Enable profiling via web interface host:port/debug/pprof/")
|
||||||
fs.BoolVar(&o.EnableContentionProfiling, "contention-profiling", o.EnableContentionProfiling,
|
fs.BoolVar(&o.EnableContentionProfiling, "contention-profiling", o.EnableContentionProfiling,
|
||||||
"Enable lock contention profiling, if profiling is enabled")
|
"Enable lock contention profiling, if profiling is enabled")
|
||||||
fs.BoolVar(&o.EnableSwaggerUI, "enable-swagger-ui", o.EnableSwaggerUI,
|
|
||||||
"Enables swagger ui on the apiserver at /swagger-ui")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *FeatureOptions) ApplyTo(c *server.Config) error {
|
func (o *FeatureOptions) ApplyTo(c *server.Config) error {
|
||||||
|
@ -59,7 +55,6 @@ func (o *FeatureOptions) ApplyTo(c *server.Config) error {
|
||||||
|
|
||||||
c.EnableProfiling = o.EnableProfiling
|
c.EnableProfiling = o.EnableProfiling
|
||||||
c.EnableContentionProfiling = o.EnableContentionProfiling
|
c.EnableContentionProfiling = o.EnableContentionProfiling
|
||||||
c.EnableSwaggerUI = o.EnableSwaggerUI
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ func (o *RecommendedOptions) ApplyTo(config *server.RecommendedConfig, scheme *r
|
||||||
if err := o.SecureServing.ApplyTo(&config.Config.SecureServing, &config.Config.LoopbackClientConfig); err != nil {
|
if err := o.SecureServing.ApplyTo(&config.Config.SecureServing, &config.Config.LoopbackClientConfig); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := o.Authentication.ApplyTo(&config.Config.Authentication, config.SecureServing, config.OpenAPIConfig); err != nil {
|
if err := o.Authentication.ApplyTo(&config.Config.Authentication, config.SecureServing); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := o.Authorization.ApplyTo(&config.Config.Authorization); err != nil {
|
if err := o.Authorization.ApplyTo(&config.Config.Authorization); err != nil {
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["datafile.go"],
|
|
||||||
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/server/routes/data/swagger",
|
|
||||||
importpath = "k8s.io/apiserver/pkg/server/routes/data/swagger",
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
File diff suppressed because one or more lines are too long
|
@ -1,46 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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 routes
|
|
||||||
|
|
||||||
import (
|
|
||||||
restful "github.com/emicklei/go-restful"
|
|
||||||
"k8s.io/klog"
|
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/server/mux"
|
|
||||||
"k8s.io/kube-openapi/pkg/common"
|
|
||||||
"k8s.io/kube-openapi/pkg/handler"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OpenAPI installs spec endpoints for each web service.
|
|
||||||
type OpenAPI struct {
|
|
||||||
Config *common.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install adds the SwaggerUI webservice to the given mux.
|
|
||||||
func (oa OpenAPI) Install(c *restful.Container, mux *mux.PathRecorderMux) {
|
|
||||||
// NOTE: [DEPRECATION] We will announce deprecation for format-separated endpoints for OpenAPI spec,
|
|
||||||
// and switch to a single /openapi/v2 endpoint in Kubernetes 1.10. The design doc and deprecation process
|
|
||||||
// are tracked at: https://docs.google.com/document/d/19lEqE9lc4yHJ3WJAJxS_G7TcORIJXGHyq3wpwcH28nU.
|
|
||||||
_, err := handler.BuildAndRegisterOpenAPIService("/swagger.json", c.RegisteredWebServices(), oa.Config, mux)
|
|
||||||
if err != nil {
|
|
||||||
klog.Fatalf("Failed to register open api spec for root: %v", err)
|
|
||||||
}
|
|
||||||
_, err = handler.BuildAndRegisterOpenAPIVersionedService("/openapi/v2", c.RegisteredWebServices(), oa.Config, mux)
|
|
||||||
if err != nil {
|
|
||||||
klog.Fatalf("Failed to register versioned open api spec for root: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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 routes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"github.com/emicklei/go-restful-swagger12"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Swagger installs the /swaggerapi/ endpoint to allow schema discovery
|
|
||||||
// and traversal. It is optional to allow consumers of the Kubernetes GenericAPIServer to
|
|
||||||
// register their own web services into the Kubernetes mux prior to initialization
|
|
||||||
// of swagger, so that other resource types show up in the documentation.
|
|
||||||
type Swagger struct {
|
|
||||||
Config *swagger.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install adds the SwaggerUI webservice to the given mux.
|
|
||||||
func (s Swagger) Install(c *restful.Container) {
|
|
||||||
s.Config.WebServices = c.RegisteredWebServices()
|
|
||||||
swagger.RegisterSwaggerService(*s.Config, c)
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2014 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 routes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/server/mux"
|
|
||||||
"k8s.io/apiserver/pkg/server/routes/data/swagger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SwaggerUI exposes files in third_party/swagger-ui/ under /swagger-ui.
|
|
||||||
type SwaggerUI struct{}
|
|
||||||
|
|
||||||
// Install adds the SwaggerUI webservice to the given mux.
|
|
||||||
func (l SwaggerUI) Install(c *mux.PathRecorderMux) {
|
|
||||||
fileServer := http.FileServer(&assetfs.AssetFS{
|
|
||||||
Asset: swagger.Asset,
|
|
||||||
AssetDir: swagger.AssetDir,
|
|
||||||
Prefix: "third_party/swagger-ui",
|
|
||||||
})
|
|
||||||
prefix := "/swagger-ui/"
|
|
||||||
c.HandlePrefix(prefix, http.StripPrefix(prefix, fileServer))
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["proto.go"],
|
|
||||||
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/util/openapi",
|
|
||||||
importpath = "k8s.io/apiserver/pkg/util/openapi",
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
deps = [
|
|
||||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
|
||||||
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
|
||||||
"//vendor/github.com/googleapis/gnostic/compiler:go_default_library",
|
|
||||||
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = ["proto_test.go"],
|
|
||||||
embed = [":go_default_library"],
|
|
||||||
deps = [
|
|
||||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
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 openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
|
|
||||||
"github.com/googleapis/gnostic/compiler"
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ToProtoModels builds the proto formatted models from OpenAPI spec
|
|
||||||
func ToProtoModels(openAPISpec *spec.Swagger) (proto.Models, error) {
|
|
||||||
specBytes, err := json.MarshalIndent(openAPISpec, " ", " ")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var info yaml.MapSlice
|
|
||||||
err = yaml.Unmarshal(specBytes, &info)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
doc, err := openapi_v2.NewDocument(info, compiler.NewContext("$root", nil))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
models, err := proto.NewOpenAPIData(doc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return models, nil
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
/*
|
|
||||||
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 openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestOpenAPIDefinitionsToProtoSchema tests the openapi parser
|
|
||||||
func TestOpenAPIDefinitionsToProtoModels(t *testing.T) {
|
|
||||||
openAPISpec := &spec.Swagger{
|
|
||||||
SwaggerProps: spec.SwaggerProps{
|
|
||||||
Swagger: "2.0",
|
|
||||||
Info: &spec.Info{
|
|
||||||
InfoProps: spec.InfoProps{
|
|
||||||
Title: "Kubernetes",
|
|
||||||
Version: "0.0.0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Definitions: spec.Definitions{
|
|
||||||
"io.k8s.api.testgroup.v1.Foo": spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Description of Foos",
|
|
||||||
Properties: map[string]spec.Schema{},
|
|
||||||
},
|
|
||||||
VendorExtensible: spec.VendorExtensible{
|
|
||||||
Extensions: spec.Extensions{
|
|
||||||
"x-kubernetes-group-version-kind": []map[string]string{
|
|
||||||
{
|
|
||||||
"group": "testgroup.k8s.io",
|
|
||||||
"version": "v1",
|
|
||||||
"kind": "Foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
expectedSchema := &proto.Arbitrary{
|
|
||||||
BaseSchema: proto.BaseSchema{
|
|
||||||
Description: "Description of Foos",
|
|
||||||
Extensions: map[string]interface{}{
|
|
||||||
"x-kubernetes-group-version-kind": []interface{}{
|
|
||||||
map[interface{}]interface{}{
|
|
||||||
"group": "testgroup.k8s.io",
|
|
||||||
"version": "v1",
|
|
||||||
"kind": "Foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Path: proto.NewPath("io.k8s.api.testgroup.v1.Foo"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
protoModels, err := ToProtoModels(openAPISpec)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected ToProtoModels not to return an error")
|
|
||||||
}
|
|
||||||
actualSchema := protoModels.LookupModel("io.k8s.api.testgroup.v1.Foo")
|
|
||||||
if !reflect.DeepEqual(expectedSchema, actualSchema) {
|
|
||||||
t.Fatalf("expected schema:\n%v\nbut got:\n%v", expectedSchema, actualSchema)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -34,7 +34,6 @@ import (
|
||||||
"k8s.io/kube-aggregator/pkg/client/clientset_generated/internalclientset"
|
"k8s.io/kube-aggregator/pkg/client/clientset_generated/internalclientset"
|
||||||
informers "k8s.io/kube-aggregator/pkg/client/informers/internalversion"
|
informers "k8s.io/kube-aggregator/pkg/client/informers/internalversion"
|
||||||
listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/internalversion"
|
listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/internalversion"
|
||||||
openapicontroller "k8s.io/kube-aggregator/pkg/controllers/openapi"
|
|
||||||
statuscontrollers "k8s.io/kube-aggregator/pkg/controllers/status"
|
statuscontrollers "k8s.io/kube-aggregator/pkg/controllers/status"
|
||||||
apiservicerest "k8s.io/kube-aggregator/pkg/registry/apiservice/rest"
|
apiservicerest "k8s.io/kube-aggregator/pkg/registry/apiservice/rest"
|
||||||
)
|
)
|
||||||
|
@ -111,8 +110,6 @@ type APIAggregator struct {
|
||||||
|
|
||||||
// Information needed to determine routing for the aggregator
|
// Information needed to determine routing for the aggregator
|
||||||
serviceResolver ServiceResolver
|
serviceResolver ServiceResolver
|
||||||
|
|
||||||
openAPIAggregationController *openapicontroller.AggregationController
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
|
// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
|
||||||
|
@ -135,9 +132,6 @@ func (cfg *Config) Complete() CompletedConfig {
|
||||||
func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.DelegationTarget) (*APIAggregator, error) {
|
func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.DelegationTarget) (*APIAggregator, error) {
|
||||||
// Prevent generic API server to install OpenAPI handler. Aggregator server
|
// Prevent generic API server to install OpenAPI handler. Aggregator server
|
||||||
// has its own customized OpenAPI handler.
|
// has its own customized OpenAPI handler.
|
||||||
openApiConfig := c.GenericConfig.OpenAPIConfig
|
|
||||||
c.GenericConfig.OpenAPIConfig = nil
|
|
||||||
|
|
||||||
genericServer, err := c.GenericConfig.New("kube-aggregator", delegationTarget)
|
genericServer, err := c.GenericConfig.New("kube-aggregator", delegationTarget)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -202,25 +196,6 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if openApiConfig != nil {
|
|
||||||
specDownloader := openapicontroller.NewDownloader()
|
|
||||||
openAPIAggregator, err := openapicontroller.BuildAndRegisterAggregator(
|
|
||||||
&specDownloader,
|
|
||||||
delegationTarget,
|
|
||||||
s.GenericAPIServer.Handler.GoRestfulContainer.RegisteredWebServices(),
|
|
||||||
openApiConfig,
|
|
||||||
s.GenericAPIServer.Handler.NonGoRestfulMux)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s.openAPIAggregationController = openapicontroller.NewAggregationController(&specDownloader, openAPIAggregator)
|
|
||||||
|
|
||||||
s.GenericAPIServer.AddPostStartHook("apiservice-openapi-controller", func(context genericapiserver.PostStartHookContext) error {
|
|
||||||
go s.openAPIAggregationController.Run(context.StopCh)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,9 +206,6 @@ func (s *APIAggregator) AddAPIService(apiService *apiregistration.APIService) er
|
||||||
// since they are wired against listers because they require multiple resources to respond
|
// since they are wired against listers because they require multiple resources to respond
|
||||||
if proxyHandler, exists := s.proxyHandlers[apiService.Name]; exists {
|
if proxyHandler, exists := s.proxyHandlers[apiService.Name]; exists {
|
||||||
proxyHandler.updateAPIService(apiService)
|
proxyHandler.updateAPIService(apiService)
|
||||||
if s.openAPIAggregationController != nil {
|
|
||||||
s.openAPIAggregationController.UpdateAPIService(proxyHandler, apiService)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,9 +224,6 @@ func (s *APIAggregator) AddAPIService(apiService *apiregistration.APIService) er
|
||||||
serviceResolver: s.serviceResolver,
|
serviceResolver: s.serviceResolver,
|
||||||
}
|
}
|
||||||
proxyHandler.updateAPIService(apiService)
|
proxyHandler.updateAPIService(apiService)
|
||||||
if s.openAPIAggregationController != nil {
|
|
||||||
s.openAPIAggregationController.AddAPIService(proxyHandler, apiService)
|
|
||||||
}
|
|
||||||
s.proxyHandlers[apiService.Name] = proxyHandler
|
s.proxyHandlers[apiService.Name] = proxyHandler
|
||||||
s.GenericAPIServer.Handler.NonGoRestfulMux.Handle(proxyPath, proxyHandler)
|
s.GenericAPIServer.Handler.NonGoRestfulMux.Handle(proxyPath, proxyHandler)
|
||||||
s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandlePrefix(proxyPath+"/", proxyHandler)
|
s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandlePrefix(proxyPath+"/", proxyHandler)
|
||||||
|
@ -296,9 +265,6 @@ func (s *APIAggregator) RemoveAPIService(apiServiceName string) {
|
||||||
}
|
}
|
||||||
s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath)
|
s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath)
|
||||||
s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath + "/")
|
s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath + "/")
|
||||||
if s.openAPIAggregationController != nil {
|
|
||||||
s.openAPIAggregationController.RemoveAPIService(apiServiceName)
|
|
||||||
}
|
|
||||||
delete(s.proxyHandlers, apiServiceName)
|
delete(s.proxyHandlers, apiServiceName)
|
||||||
|
|
||||||
// TODO unregister group level discovery when there are no more versions for the group
|
// TODO unregister group level discovery when there are no more versions for the group
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = [
|
|
||||||
"aggregator.go",
|
|
||||||
"controller.go",
|
|
||||||
"downloader.go",
|
|
||||||
],
|
|
||||||
importmap = "k8s.io/kubernetes/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi",
|
|
||||||
importpath = "k8s.io/kube-aggregator/pkg/controllers/openapi",
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
deps = [
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apiserver/pkg/server:go_default_library",
|
|
||||||
"//staging/src/k8s.io/client-go/util/workqueue:go_default_library",
|
|
||||||
"//staging/src/k8s.io/kube-aggregator/pkg/apis/apiregistration:go_default_library",
|
|
||||||
"//vendor/github.com/emicklei/go-restful:go_default_library",
|
|
||||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
|
||||||
"//vendor/k8s.io/klog:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/aggregator:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/builder:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/handler:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = ["aggregator_test.go"],
|
|
||||||
embed = [":go_default_library"],
|
|
||||||
deps = [
|
|
||||||
"//staging/src/k8s.io/kube-aggregator/pkg/apis/apiregistration:go_default_library",
|
|
||||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
|
||||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
|
@ -1,338 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
restful "github.com/emicklei/go-restful"
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/server"
|
|
||||||
"k8s.io/kube-aggregator/pkg/apis/apiregistration"
|
|
||||||
"k8s.io/kube-openapi/pkg/aggregator"
|
|
||||||
"k8s.io/kube-openapi/pkg/builder"
|
|
||||||
"k8s.io/kube-openapi/pkg/common"
|
|
||||||
"k8s.io/kube-openapi/pkg/handler"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
aggregatorUser = "system:aggregator"
|
|
||||||
specDownloadTimeout = 60 * time.Second
|
|
||||||
localDelegateChainNamePattern = "k8s_internal_local_delegation_chain_%010d"
|
|
||||||
|
|
||||||
// A randomly generated UUID to differentiate local and remote eTags.
|
|
||||||
locallyGeneratedEtagPrefix = "\"6E8F849B434D4B98A569B9D7718876E9-"
|
|
||||||
)
|
|
||||||
|
|
||||||
type specAggregator struct {
|
|
||||||
// mutex protects all members of this struct.
|
|
||||||
rwMutex sync.RWMutex
|
|
||||||
|
|
||||||
// Map of API Services' OpenAPI specs by their name
|
|
||||||
openAPISpecs map[string]*openAPISpecInfo
|
|
||||||
|
|
||||||
// provided for dynamic OpenAPI spec
|
|
||||||
openAPIService *handler.OpenAPIService
|
|
||||||
openAPIVersionedService *handler.OpenAPIService
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ AggregationManager = &specAggregator{}
|
|
||||||
|
|
||||||
// This function is not thread safe as it only being called on startup.
|
|
||||||
func (s *specAggregator) addLocalSpec(spec *spec.Swagger, localHandler http.Handler, name, etag string) {
|
|
||||||
localAPIService := apiregistration.APIService{}
|
|
||||||
localAPIService.Name = name
|
|
||||||
s.openAPISpecs[name] = &openAPISpecInfo{
|
|
||||||
etag: etag,
|
|
||||||
apiService: localAPIService,
|
|
||||||
handler: localHandler,
|
|
||||||
spec: spec,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildAndRegisterAggregator registered OpenAPI aggregator handler. This function is not thread safe as it only being called on startup.
|
|
||||||
func BuildAndRegisterAggregator(downloader *Downloader, delegationTarget server.DelegationTarget, webServices []*restful.WebService,
|
|
||||||
config *common.Config, pathHandler common.PathHandler) (AggregationManager, error) {
|
|
||||||
s := &specAggregator{
|
|
||||||
openAPISpecs: map[string]*openAPISpecInfo{},
|
|
||||||
}
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
// Build Aggregator's spec
|
|
||||||
aggregatorOpenAPISpec, err := builder.BuildOpenAPISpec(
|
|
||||||
webServices, config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reserving non-name spec for aggregator's Spec.
|
|
||||||
s.addLocalSpec(aggregatorOpenAPISpec, nil, fmt.Sprintf(localDelegateChainNamePattern, i), "")
|
|
||||||
i++
|
|
||||||
for delegate := delegationTarget; delegate != nil; delegate = delegate.NextDelegate() {
|
|
||||||
handler := delegate.UnprotectedHandler()
|
|
||||||
if handler == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delegateSpec, etag, _, err := downloader.Download(handler, "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if delegateSpec == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s.addLocalSpec(delegateSpec, handler, fmt.Sprintf(localDelegateChainNamePattern, i), etag)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build initial spec to serve.
|
|
||||||
specToServe, err := s.buildOpenAPISpec()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install handler
|
|
||||||
// NOTE: [DEPRECATION] We will announce deprecation for format-separated endpoints for OpenAPI spec,
|
|
||||||
// and switch to a single /openapi/v2 endpoint in Kubernetes 1.10. The design doc and deprecation process
|
|
||||||
// are tracked at: https://docs.google.com/document/d/19lEqE9lc4yHJ3WJAJxS_G7TcORIJXGHyq3wpwcH28nU.
|
|
||||||
s.openAPIService, err = handler.RegisterOpenAPIService(
|
|
||||||
specToServe, "/swagger.json", pathHandler)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s.openAPIVersionedService, err = handler.RegisterOpenAPIVersionedService(
|
|
||||||
specToServe, "/openapi/v2", pathHandler)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// openAPISpecInfo is used to store OpenAPI spec with its priority.
|
|
||||||
// It can be used to sort specs with their priorities.
|
|
||||||
type openAPISpecInfo struct {
|
|
||||||
apiService apiregistration.APIService
|
|
||||||
|
|
||||||
// Specification of this API Service. If null then the spec is not loaded yet.
|
|
||||||
spec *spec.Swagger
|
|
||||||
handler http.Handler
|
|
||||||
etag string
|
|
||||||
}
|
|
||||||
|
|
||||||
// byPriority can be used in sort.Sort to sort specs with their priorities.
|
|
||||||
type byPriority struct {
|
|
||||||
specs []openAPISpecInfo
|
|
||||||
groupPriorities map[string]int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a byPriority) Len() int { return len(a.specs) }
|
|
||||||
func (a byPriority) Swap(i, j int) { a.specs[i], a.specs[j] = a.specs[j], a.specs[i] }
|
|
||||||
func (a byPriority) Less(i, j int) bool {
|
|
||||||
// All local specs will come first
|
|
||||||
// WARNING: This will result in not following priorities for local APIServices.
|
|
||||||
if a.specs[i].apiService.Spec.Service == nil {
|
|
||||||
// Sort local specs with their name. This is the order in the delegation chain (aggregator first).
|
|
||||||
return a.specs[i].apiService.Name < a.specs[j].apiService.Name
|
|
||||||
}
|
|
||||||
var iPriority, jPriority int32
|
|
||||||
if a.specs[i].apiService.Spec.Group == a.specs[j].apiService.Spec.Group {
|
|
||||||
iPriority = a.specs[i].apiService.Spec.VersionPriority
|
|
||||||
jPriority = a.specs[i].apiService.Spec.VersionPriority
|
|
||||||
} else {
|
|
||||||
iPriority = a.groupPriorities[a.specs[i].apiService.Spec.Group]
|
|
||||||
jPriority = a.groupPriorities[a.specs[j].apiService.Spec.Group]
|
|
||||||
}
|
|
||||||
if iPriority != jPriority {
|
|
||||||
// Sort by priority, higher first
|
|
||||||
return iPriority > jPriority
|
|
||||||
}
|
|
||||||
// Sort by service name.
|
|
||||||
return a.specs[i].apiService.Name < a.specs[j].apiService.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortByPriority(specs []openAPISpecInfo) {
|
|
||||||
b := byPriority{
|
|
||||||
specs: specs,
|
|
||||||
groupPriorities: map[string]int32{},
|
|
||||||
}
|
|
||||||
for _, spec := range specs {
|
|
||||||
if spec.apiService.Spec.Service == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if pr, found := b.groupPriorities[spec.apiService.Spec.Group]; !found || spec.apiService.Spec.GroupPriorityMinimum > pr {
|
|
||||||
b.groupPriorities[spec.apiService.Spec.Group] = spec.apiService.Spec.GroupPriorityMinimum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Sort(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildOpenAPISpec aggregates all OpenAPI specs. It is not thread-safe. The caller is responsible to hold proper locks.
|
|
||||||
func (s *specAggregator) buildOpenAPISpec() (specToReturn *spec.Swagger, err error) {
|
|
||||||
specs := []openAPISpecInfo{}
|
|
||||||
for _, specInfo := range s.openAPISpecs {
|
|
||||||
if specInfo.spec == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
specs = append(specs, *specInfo)
|
|
||||||
}
|
|
||||||
if len(specs) == 0 {
|
|
||||||
return &spec.Swagger{}, nil
|
|
||||||
}
|
|
||||||
sortByPriority(specs)
|
|
||||||
for _, specInfo := range specs {
|
|
||||||
// TODO: Make kube-openapi.MergeSpec(s) accept nil or empty spec as destination and just clone the spec in that case.
|
|
||||||
if specToReturn == nil {
|
|
||||||
specToReturn, err = aggregator.CloneSpec(specInfo.spec)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := aggregator.MergeSpecsIgnorePathConflict(specToReturn, specInfo.spec); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return specToReturn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateOpenAPISpec aggregates all OpenAPI specs. It is not thread-safe. The caller is responsible to hold proper locks.
|
|
||||||
func (s *specAggregator) updateOpenAPISpec() error {
|
|
||||||
if s.openAPIService == nil || s.openAPIVersionedService == nil {
|
|
||||||
// openAPIVersionedService and deprecated openAPIService should be initialized together
|
|
||||||
if !(s.openAPIService == nil && s.openAPIVersionedService == nil) {
|
|
||||||
return fmt.Errorf("unexpected openapi service initialization error")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
specToServe, err := s.buildOpenAPISpec()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// openAPIService.UpdateSpec and openAPIVersionedService.UpdateSpec read the same swagger spec
|
|
||||||
// serially and update their local caches separately. Both endpoints will have same spec in
|
|
||||||
// their caches if the caller is holding proper locks.
|
|
||||||
err = s.openAPIService.UpdateSpec(specToServe)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.openAPIVersionedService.UpdateSpec(specToServe)
|
|
||||||
}
|
|
||||||
|
|
||||||
// tryUpdatingServiceSpecs tries updating openAPISpecs map with specified specInfo, and keeps the map intact
|
|
||||||
// if the update fails.
|
|
||||||
func (s *specAggregator) tryUpdatingServiceSpecs(specInfo *openAPISpecInfo) error {
|
|
||||||
orgSpecInfo, exists := s.openAPISpecs[specInfo.apiService.Name]
|
|
||||||
s.openAPISpecs[specInfo.apiService.Name] = specInfo
|
|
||||||
if err := s.updateOpenAPISpec(); err != nil {
|
|
||||||
if exists {
|
|
||||||
s.openAPISpecs[specInfo.apiService.Name] = orgSpecInfo
|
|
||||||
} else {
|
|
||||||
delete(s.openAPISpecs, specInfo.apiService.Name)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// tryDeleteServiceSpecs tries delete specified specInfo from openAPISpecs map, and keeps the map intact
|
|
||||||
// if the update fails.
|
|
||||||
func (s *specAggregator) tryDeleteServiceSpecs(apiServiceName string) error {
|
|
||||||
orgSpecInfo, exists := s.openAPISpecs[apiServiceName]
|
|
||||||
if !exists {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
delete(s.openAPISpecs, apiServiceName)
|
|
||||||
if err := s.updateOpenAPISpec(); err != nil {
|
|
||||||
s.openAPISpecs[apiServiceName] = orgSpecInfo
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateAPIServiceSpec updates the api service's OpenAPI spec. It is thread safe.
|
|
||||||
func (s *specAggregator) UpdateAPIServiceSpec(apiServiceName string, spec *spec.Swagger, etag string) error {
|
|
||||||
s.rwMutex.Lock()
|
|
||||||
defer s.rwMutex.Unlock()
|
|
||||||
|
|
||||||
specInfo, existingService := s.openAPISpecs[apiServiceName]
|
|
||||||
if !existingService {
|
|
||||||
return fmt.Errorf("APIService %q does not exists", apiServiceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For APIServices (non-local) specs, only merge their /apis/ prefixed endpoint as it is the only paths
|
|
||||||
// proxy handler delegates.
|
|
||||||
if specInfo.apiService.Spec.Service != nil {
|
|
||||||
aggregator.FilterSpecByPaths(spec, []string{"/apis/"})
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.tryUpdatingServiceSpecs(&openAPISpecInfo{
|
|
||||||
apiService: specInfo.apiService,
|
|
||||||
spec: spec,
|
|
||||||
handler: specInfo.handler,
|
|
||||||
etag: etag,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddUpdateAPIService adds or updates the api service. It is thread safe.
|
|
||||||
func (s *specAggregator) AddUpdateAPIService(handler http.Handler, apiService *apiregistration.APIService) error {
|
|
||||||
s.rwMutex.Lock()
|
|
||||||
defer s.rwMutex.Unlock()
|
|
||||||
|
|
||||||
if apiService.Spec.Service == nil {
|
|
||||||
// All local specs should be already aggregated using local delegate chain
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newSpec := &openAPISpecInfo{
|
|
||||||
apiService: *apiService,
|
|
||||||
handler: handler,
|
|
||||||
}
|
|
||||||
if specInfo, existingService := s.openAPISpecs[apiService.Name]; existingService {
|
|
||||||
newSpec.etag = specInfo.etag
|
|
||||||
newSpec.spec = specInfo.spec
|
|
||||||
}
|
|
||||||
return s.tryUpdatingServiceSpecs(newSpec)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveAPIServiceSpec removes an api service from OpenAPI aggregation. If it does not exist, no error is returned.
|
|
||||||
// It is thread safe.
|
|
||||||
func (s *specAggregator) RemoveAPIServiceSpec(apiServiceName string) error {
|
|
||||||
s.rwMutex.Lock()
|
|
||||||
defer s.rwMutex.Unlock()
|
|
||||||
|
|
||||||
if _, existingService := s.openAPISpecs[apiServiceName]; !existingService {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.tryDeleteServiceSpecs(apiServiceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAPIServiceSpec returns api service spec info
|
|
||||||
func (s *specAggregator) GetAPIServiceInfo(apiServiceName string) (handler http.Handler, etag string, exists bool) {
|
|
||||||
s.rwMutex.RLock()
|
|
||||||
defer s.rwMutex.RUnlock()
|
|
||||||
|
|
||||||
if info, existingService := s.openAPISpecs[apiServiceName]; existingService {
|
|
||||||
return info.handler, info.etag, true
|
|
||||||
}
|
|
||||||
return nil, "", false
|
|
||||||
}
|
|
|
@ -1,165 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"k8s.io/kube-aggregator/pkg/apis/apiregistration"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newAPIServiceForTest(name, group string, minGroupPriority, versionPriority int32) apiregistration.APIService {
|
|
||||||
r := apiregistration.APIService{}
|
|
||||||
r.Spec.Group = group
|
|
||||||
r.Spec.GroupPriorityMinimum = minGroupPriority
|
|
||||||
r.Spec.VersionPriority = versionPriority
|
|
||||||
r.Spec.Service = &apiregistration.ServiceReference{}
|
|
||||||
r.Name = name
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertSortedServices(t *testing.T, actual []openAPISpecInfo, expectedNames []string) {
|
|
||||||
actualNames := []string{}
|
|
||||||
for _, a := range actual {
|
|
||||||
actualNames = append(actualNames, a.apiService.Name)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(actualNames, expectedNames) {
|
|
||||||
t.Errorf("Expected %s got %s.", expectedNames, actualNames)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAPIServiceSort(t *testing.T) {
|
|
||||||
list := []openAPISpecInfo{
|
|
||||||
{
|
|
||||||
apiService: newAPIServiceForTest("FirstService", "Group1", 10, 5),
|
|
||||||
spec: &spec.Swagger{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
apiService: newAPIServiceForTest("SecondService", "Group2", 15, 3),
|
|
||||||
spec: &spec.Swagger{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
apiService: newAPIServiceForTest("FirstServiceInternal", "Group1", 16, 3),
|
|
||||||
spec: &spec.Swagger{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
apiService: newAPIServiceForTest("ThirdService", "Group3", 15, 3),
|
|
||||||
spec: &spec.Swagger{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
sortByPriority(list)
|
|
||||||
assertSortedServices(t, list, []string{"FirstService", "FirstServiceInternal", "SecondService", "ThirdService"})
|
|
||||||
}
|
|
||||||
|
|
||||||
type handlerTest struct {
|
|
||||||
etag string
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ http.Handler = handlerTest{}
|
|
||||||
|
|
||||||
func (h handlerTest) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if len(h.etag) > 0 {
|
|
||||||
w.Header().Add("Etag", h.etag)
|
|
||||||
}
|
|
||||||
ifNoneMatches := r.Header["If-None-Match"]
|
|
||||||
for _, match := range ifNoneMatches {
|
|
||||||
if match == h.etag {
|
|
||||||
w.WriteHeader(http.StatusNotModified)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.Write(h.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
type handlerDeprecatedTest struct {
|
|
||||||
etag string
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ http.Handler = handlerDeprecatedTest{}
|
|
||||||
|
|
||||||
func (h handlerDeprecatedTest) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// old server returns 403 on new endpoint
|
|
||||||
if r.URL.Path == "/openapi/v2" {
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(h.etag) > 0 {
|
|
||||||
w.Header().Add("Etag", h.etag)
|
|
||||||
}
|
|
||||||
ifNoneMatches := r.Header["If-None-Match"]
|
|
||||||
for _, match := range ifNoneMatches {
|
|
||||||
if match == h.etag {
|
|
||||||
w.WriteHeader(http.StatusNotModified)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.Write(h.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertDownloadedSpec(actualSpec *spec.Swagger, actualEtag string, err error,
|
|
||||||
expectedSpecID string, expectedEtag string) error {
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("downloadOpenAPISpec failed : %s", err)
|
|
||||||
}
|
|
||||||
if expectedSpecID == "" && actualSpec != nil {
|
|
||||||
return fmt.Errorf("expected Not Modified, actual ID %s", actualSpec.ID)
|
|
||||||
}
|
|
||||||
if actualSpec != nil && actualSpec.ID != expectedSpecID {
|
|
||||||
return fmt.Errorf("expected ID %s, actual ID %s", expectedSpecID, actualSpec.ID)
|
|
||||||
}
|
|
||||||
if actualEtag != expectedEtag {
|
|
||||||
return fmt.Errorf("expected ETag '%s', actual ETag '%s'", expectedEtag, actualEtag)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDownloadOpenAPISpec(t *testing.T) {
|
|
||||||
|
|
||||||
s := Downloader{}
|
|
||||||
|
|
||||||
// Test with no eTag
|
|
||||||
actualSpec, actualEtag, _, err := s.Download(handlerTest{data: []byte("{\"id\": \"test\"}")}, "")
|
|
||||||
assert.NoError(t, assertDownloadedSpec(actualSpec, actualEtag, err, "test", "\"6E8F849B434D4B98A569B9D7718876E9-356ECAB19D7FBE1336BABB1E70F8F3025050DE218BE78256BE81620681CFC9A268508E542B8B55974E17B2184BBFC8FFFAA577E51BE195D32B3CA2547818ABE4\""))
|
|
||||||
|
|
||||||
// Test with eTag
|
|
||||||
actualSpec, actualEtag, _, err = s.Download(
|
|
||||||
handlerTest{data: []byte("{\"id\": \"test\"}"), etag: "etag_test"}, "")
|
|
||||||
assert.NoError(t, assertDownloadedSpec(actualSpec, actualEtag, err, "test", "etag_test"))
|
|
||||||
|
|
||||||
// Test not modified
|
|
||||||
actualSpec, actualEtag, _, err = s.Download(
|
|
||||||
handlerTest{data: []byte("{\"id\": \"test\"}"), etag: "etag_test"}, "etag_test")
|
|
||||||
assert.NoError(t, assertDownloadedSpec(actualSpec, actualEtag, err, "", "etag_test"))
|
|
||||||
|
|
||||||
// Test different eTags
|
|
||||||
actualSpec, actualEtag, _, err = s.Download(
|
|
||||||
handlerTest{data: []byte("{\"id\": \"test\"}"), etag: "etag_test1"}, "etag_test2")
|
|
||||||
assert.NoError(t, assertDownloadedSpec(actualSpec, actualEtag, err, "test", "etag_test1"))
|
|
||||||
|
|
||||||
// Test old server fallback path
|
|
||||||
actualSpec, actualEtag, _, err = s.Download(handlerDeprecatedTest{data: []byte("{\"id\": \"test\"}")}, "")
|
|
||||||
assert.NoError(t, assertDownloadedSpec(actualSpec, actualEtag, err, "test", "\"6E8F849B434D4B98A569B9D7718876E9-356ECAB19D7FBE1336BABB1E70F8F3025050DE218BE78256BE81620681CFC9A268508E542B8B55974E17B2184BBFC8FFFAA577E51BE195D32B3CA2547818ABE4\""))
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,186 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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 openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
"k8s.io/klog"
|
|
||||||
|
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
"k8s.io/client-go/util/workqueue"
|
|
||||||
"k8s.io/kube-aggregator/pkg/apis/apiregistration"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
successfulUpdateDelay = time.Minute
|
|
||||||
failedUpdateMaxExpDelay = time.Hour
|
|
||||||
)
|
|
||||||
|
|
||||||
type syncAction int
|
|
||||||
|
|
||||||
const (
|
|
||||||
syncRequeue syncAction = iota
|
|
||||||
syncRequeueRateLimited
|
|
||||||
syncNothing
|
|
||||||
)
|
|
||||||
|
|
||||||
// AggregationManager is the interface between this controller and OpenAPI Aggregator service.
|
|
||||||
type AggregationManager interface {
|
|
||||||
AddUpdateAPIService(handler http.Handler, apiService *apiregistration.APIService) error
|
|
||||||
UpdateAPIServiceSpec(apiServiceName string, spec *spec.Swagger, etag string) error
|
|
||||||
RemoveAPIServiceSpec(apiServiceName string) error
|
|
||||||
GetAPIServiceInfo(apiServiceName string) (handler http.Handler, etag string, exists bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AggregationController periodically check for changes in OpenAPI specs of APIServices and update/remove
|
|
||||||
// them if necessary.
|
|
||||||
type AggregationController struct {
|
|
||||||
openAPIAggregationManager AggregationManager
|
|
||||||
queue workqueue.RateLimitingInterface
|
|
||||||
downloader *Downloader
|
|
||||||
|
|
||||||
// To allow injection for testing.
|
|
||||||
syncHandler func(key string) (syncAction, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAggregationController creates new OpenAPI aggregation controller.
|
|
||||||
func NewAggregationController(downloader *Downloader, openAPIAggregationManager AggregationManager) *AggregationController {
|
|
||||||
c := &AggregationController{
|
|
||||||
openAPIAggregationManager: openAPIAggregationManager,
|
|
||||||
queue: workqueue.NewNamedRateLimitingQueue(
|
|
||||||
workqueue.NewItemExponentialFailureRateLimiter(successfulUpdateDelay, failedUpdateMaxExpDelay), "APIServiceOpenAPIAggregationControllerQueue1"),
|
|
||||||
downloader: downloader,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.syncHandler = c.sync
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run starts OpenAPI AggregationController
|
|
||||||
func (c *AggregationController) Run(stopCh <-chan struct{}) {
|
|
||||||
defer utilruntime.HandleCrash()
|
|
||||||
defer c.queue.ShutDown()
|
|
||||||
|
|
||||||
klog.Infof("Starting OpenAPI AggregationController")
|
|
||||||
defer klog.Infof("Shutting down OpenAPI AggregationController")
|
|
||||||
|
|
||||||
go wait.Until(c.runWorker, time.Second, stopCh)
|
|
||||||
|
|
||||||
<-stopCh
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *AggregationController) runWorker() {
|
|
||||||
for c.processNextWorkItem() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit.
|
|
||||||
func (c *AggregationController) processNextWorkItem() bool {
|
|
||||||
key, quit := c.queue.Get()
|
|
||||||
defer c.queue.Done(key)
|
|
||||||
if quit {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.Infof("OpenAPI AggregationController: Processing item %s", key)
|
|
||||||
|
|
||||||
action, err := c.syncHandler(key.(string))
|
|
||||||
if err == nil {
|
|
||||||
c.queue.Forget(key)
|
|
||||||
} else {
|
|
||||||
utilruntime.HandleError(fmt.Errorf("loading OpenAPI spec for %q failed with: %v", key, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case syncRequeue:
|
|
||||||
klog.Infof("OpenAPI AggregationController: action for item %s: Requeue.", key)
|
|
||||||
c.queue.AddAfter(key, successfulUpdateDelay)
|
|
||||||
case syncRequeueRateLimited:
|
|
||||||
klog.Infof("OpenAPI AggregationController: action for item %s: Rate Limited Requeue.", key)
|
|
||||||
c.queue.AddRateLimited(key)
|
|
||||||
case syncNothing:
|
|
||||||
klog.Infof("OpenAPI AggregationController: action for item %s: Nothing (removed from the queue).", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *AggregationController) sync(key string) (syncAction, error) {
|
|
||||||
handler, etag, exists := c.openAPIAggregationManager.GetAPIServiceInfo(key)
|
|
||||||
if !exists || handler == nil {
|
|
||||||
return syncNothing, nil
|
|
||||||
}
|
|
||||||
returnSpec, newEtag, httpStatus, err := c.downloader.Download(handler, etag)
|
|
||||||
switch {
|
|
||||||
case err != nil:
|
|
||||||
return syncRequeueRateLimited, err
|
|
||||||
case httpStatus == http.StatusNotModified:
|
|
||||||
case httpStatus == http.StatusNotFound || returnSpec == nil:
|
|
||||||
return syncRequeueRateLimited, fmt.Errorf("OpenAPI spec does not exists")
|
|
||||||
case httpStatus == http.StatusOK:
|
|
||||||
if err := c.openAPIAggregationManager.UpdateAPIServiceSpec(key, returnSpec, newEtag); err != nil {
|
|
||||||
return syncRequeueRateLimited, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return syncRequeue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddAPIService adds a new API Service to OpenAPI Aggregation.
|
|
||||||
func (c *AggregationController) AddAPIService(handler http.Handler, apiService *apiregistration.APIService) {
|
|
||||||
if apiService.Spec.Service == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := c.openAPIAggregationManager.AddUpdateAPIService(handler, apiService); err != nil {
|
|
||||||
utilruntime.HandleError(fmt.Errorf("adding %q to AggregationController failed with: %v", apiService.Name, err))
|
|
||||||
}
|
|
||||||
c.queue.AddAfter(apiService.Name, time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateAPIService updates API Service's info and handler.
|
|
||||||
func (c *AggregationController) UpdateAPIService(handler http.Handler, apiService *apiregistration.APIService) {
|
|
||||||
if apiService.Spec.Service == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := c.openAPIAggregationManager.AddUpdateAPIService(handler, apiService); err != nil {
|
|
||||||
utilruntime.HandleError(fmt.Errorf("updating %q to AggregationController failed with: %v", apiService.Name, err))
|
|
||||||
}
|
|
||||||
key := apiService.Name
|
|
||||||
if c.queue.NumRequeues(key) > 0 {
|
|
||||||
// The item has failed before. Remove it from failure queue and
|
|
||||||
// update it in a second
|
|
||||||
c.queue.Forget(key)
|
|
||||||
c.queue.AddAfter(key, time.Second)
|
|
||||||
}
|
|
||||||
// Else: The item has been succeeded before and it will be updated soon (after successfulUpdateDelay)
|
|
||||||
// we don't add it again as it will cause a duplication of items.
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveAPIService removes API Service from OpenAPI Aggregation Controller.
|
|
||||||
func (c *AggregationController) RemoveAPIService(apiServiceName string) {
|
|
||||||
if err := c.openAPIAggregationManager.RemoveAPIServiceSpec(apiServiceName); err != nil {
|
|
||||||
utilruntime.HandleError(fmt.Errorf("removing %q from AggregationController failed with: %v", apiServiceName, err))
|
|
||||||
}
|
|
||||||
// This will only remove it if it was failing before. If it was successful, processNextWorkItem will figure it out
|
|
||||||
// and will not add it again to the queue.
|
|
||||||
c.queue.Forget(apiServiceName)
|
|
||||||
}
|
|
|
@ -1,160 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha512"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Downloader is the OpenAPI downloader type. It will try to download spec from /swagger.json endpoint.
|
|
||||||
type Downloader struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDownloader creates a new OpenAPI Downloader.
|
|
||||||
func NewDownloader() Downloader {
|
|
||||||
return Downloader{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// inMemoryResponseWriter is a http.Writer that keep the response in memory.
|
|
||||||
type inMemoryResponseWriter struct {
|
|
||||||
writeHeaderCalled bool
|
|
||||||
header http.Header
|
|
||||||
respCode int
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func newInMemoryResponseWriter() *inMemoryResponseWriter {
|
|
||||||
return &inMemoryResponseWriter{header: http.Header{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *inMemoryResponseWriter) Header() http.Header {
|
|
||||||
return r.header
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *inMemoryResponseWriter) WriteHeader(code int) {
|
|
||||||
r.writeHeaderCalled = true
|
|
||||||
r.respCode = code
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *inMemoryResponseWriter) Write(in []byte) (int, error) {
|
|
||||||
if !r.writeHeaderCalled {
|
|
||||||
r.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
r.data = append(r.data, in...)
|
|
||||||
return len(in), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *inMemoryResponseWriter) String() string {
|
|
||||||
s := fmt.Sprintf("ResponseCode: %d", r.respCode)
|
|
||||||
if r.data != nil {
|
|
||||||
s += fmt.Sprintf(", Body: %s", string(r.data))
|
|
||||||
}
|
|
||||||
if r.header != nil {
|
|
||||||
s += fmt.Sprintf(", Header: %s", r.header)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Downloader) handlerWithUser(handler http.Handler, info user.Info) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
||||||
req = req.WithContext(request.WithUser(req.Context(), info))
|
|
||||||
handler.ServeHTTP(w, req)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func etagFor(data []byte) string {
|
|
||||||
return fmt.Sprintf("%s%X\"", locallyGeneratedEtagPrefix, sha512.Sum512(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download downloads openAPI spec from /swagger.json endpoint of the given handler.
|
|
||||||
// httpStatus is only valid if err == nil
|
|
||||||
func (s *Downloader) Download(handler http.Handler, etag string) (returnSpec *spec.Swagger, newEtag string, httpStatus int, err error) {
|
|
||||||
handler = s.handlerWithUser(handler, &user.DefaultInfo{Name: aggregatorUser})
|
|
||||||
handler = http.TimeoutHandler(handler, specDownloadTimeout, "request timed out")
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "/openapi/v2", nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", 0, err
|
|
||||||
}
|
|
||||||
req.Header.Add("Accept", "application/json")
|
|
||||||
|
|
||||||
// Only pass eTag if it is not generated locally
|
|
||||||
if len(etag) > 0 && !strings.HasPrefix(etag, locallyGeneratedEtagPrefix) {
|
|
||||||
req.Header.Add("If-None-Match", etag)
|
|
||||||
}
|
|
||||||
|
|
||||||
writer := newInMemoryResponseWriter()
|
|
||||||
handler.ServeHTTP(writer, req)
|
|
||||||
|
|
||||||
// single endpoint not found/registered in old server, try to fetch old endpoint
|
|
||||||
// TODO(roycaihw): remove this in 1.11
|
|
||||||
if writer.respCode == http.StatusForbidden || writer.respCode == http.StatusNotFound {
|
|
||||||
req, err = http.NewRequest("GET", "/swagger.json", nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only pass eTag if it is not generated locally
|
|
||||||
if len(etag) > 0 && !strings.HasPrefix(etag, locallyGeneratedEtagPrefix) {
|
|
||||||
req.Header.Add("If-None-Match", etag)
|
|
||||||
}
|
|
||||||
|
|
||||||
writer = newInMemoryResponseWriter()
|
|
||||||
handler.ServeHTTP(writer, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch writer.respCode {
|
|
||||||
case http.StatusNotModified:
|
|
||||||
if len(etag) == 0 {
|
|
||||||
return nil, etag, http.StatusNotModified, fmt.Errorf("http.StatusNotModified is not allowed in absence of etag")
|
|
||||||
}
|
|
||||||
return nil, etag, http.StatusNotModified, nil
|
|
||||||
case http.StatusNotFound:
|
|
||||||
// Gracefully skip 404, assuming the server won't provide any spec
|
|
||||||
return nil, "", http.StatusNotFound, nil
|
|
||||||
case http.StatusOK:
|
|
||||||
openAPISpec := &spec.Swagger{}
|
|
||||||
if err := json.Unmarshal(writer.data, openAPISpec); err != nil {
|
|
||||||
return nil, "", 0, err
|
|
||||||
}
|
|
||||||
newEtag = writer.Header().Get("Etag")
|
|
||||||
if len(newEtag) == 0 {
|
|
||||||
newEtag = etagFor(writer.data)
|
|
||||||
if len(etag) > 0 && strings.HasPrefix(etag, locallyGeneratedEtagPrefix) {
|
|
||||||
// The function call with an etag and server does not report an etag.
|
|
||||||
// That means this server does not support etag and the etag that passed
|
|
||||||
// to the function generated previously by us. Just compare etags and
|
|
||||||
// return StatusNotModified if they are the same.
|
|
||||||
if etag == newEtag {
|
|
||||||
return nil, etag, http.StatusNotModified, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return openAPISpec, newEtag, http.StatusOK, nil
|
|
||||||
default:
|
|
||||||
return nil, "", 0, fmt.Errorf("failed to retrieve openAPI spec, http error: %s", writer.String())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.x
|
|
|
@ -1,38 +0,0 @@
|
||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = [
|
|
||||||
"api_declaration_list.go",
|
|
||||||
"config.go",
|
|
||||||
"model_builder.go",
|
|
||||||
"model_list.go",
|
|
||||||
"model_property_ext.go",
|
|
||||||
"model_property_list.go",
|
|
||||||
"ordered_route_map.go",
|
|
||||||
"swagger.go",
|
|
||||||
"swagger_builder.go",
|
|
||||||
"swagger_webservice.go",
|
|
||||||
],
|
|
||||||
importmap = "k8s.io/kubernetes/vendor/github.com/emicklei/go-restful-swagger12",
|
|
||||||
importpath = "github.com/emicklei/go-restful-swagger12",
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
deps = [
|
|
||||||
"//vendor/github.com/emicklei/go-restful:go_default_library",
|
|
||||||
"//vendor/github.com/emicklei/go-restful/log:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue