change cluster schema (#2026)
* change cluster schema * change cluster schema
This commit is contained in:
153
vendor/sigs.k8s.io/kubefed/pkg/kubefedctl/enable/deprecatedapis.go
generated
vendored
Normal file
153
vendor/sigs.k8s.io/kubefed/pkg/kubefedctl/enable/deprecatedapis.go
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
Copyright 2019 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 enable
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
|
||||
)
|
||||
|
||||
// Deprecated APIs removed in 1.16 will be served by current equivalent APIs
|
||||
// https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/
|
||||
//
|
||||
// Only allow one of the equivalent APIs for federation to avoid the possibility
|
||||
// of multiple sync controllers fighting to update the same resource
|
||||
var equivalentAPIs = map[string][]schema.GroupVersion{
|
||||
"deployments": {
|
||||
{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
},
|
||||
{
|
||||
Group: "apps",
|
||||
Version: "v1beta1",
|
||||
},
|
||||
{
|
||||
Group: "apps",
|
||||
Version: "v1beta2",
|
||||
},
|
||||
{
|
||||
Group: "extensions",
|
||||
Version: "v1beta1",
|
||||
},
|
||||
},
|
||||
"daemonsets": {
|
||||
{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
},
|
||||
{
|
||||
Group: "apps",
|
||||
Version: "v1beta1",
|
||||
},
|
||||
{
|
||||
Group: "apps",
|
||||
Version: "v1beta2",
|
||||
},
|
||||
{
|
||||
Group: "extensions",
|
||||
Version: "v1beta1",
|
||||
},
|
||||
},
|
||||
"statefulsets": {
|
||||
{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
},
|
||||
{
|
||||
Group: "apps",
|
||||
Version: "v1beta1",
|
||||
},
|
||||
{
|
||||
Group: "apps",
|
||||
Version: "v1beta2",
|
||||
},
|
||||
},
|
||||
"replicasets": {
|
||||
{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
},
|
||||
{
|
||||
Group: "apps",
|
||||
Version: "v1beta1",
|
||||
},
|
||||
{
|
||||
Group: "apps",
|
||||
Version: "v1beta2",
|
||||
},
|
||||
{
|
||||
Group: "extensions",
|
||||
Version: "v1beta1",
|
||||
},
|
||||
},
|
||||
"networkpolicies": {
|
||||
{
|
||||
Group: "networking.k8s.io",
|
||||
Version: "v1",
|
||||
},
|
||||
{
|
||||
Group: "extensions",
|
||||
Version: "v1beta1",
|
||||
},
|
||||
},
|
||||
"podsecuritypolicies": {
|
||||
{
|
||||
Group: "policy",
|
||||
Version: "v1beta1",
|
||||
},
|
||||
{
|
||||
Group: "extensions",
|
||||
Version: "v1beta1",
|
||||
},
|
||||
},
|
||||
"ingresses": {
|
||||
{
|
||||
Group: "networking.k8s.io",
|
||||
Version: "v1beta1",
|
||||
},
|
||||
{
|
||||
Group: "extensions",
|
||||
Version: "v1beta1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func IsEquivalentAPI(existingAPI, newAPI *fedv1b1.APIResource) bool {
|
||||
if existingAPI.PluralName != newAPI.PluralName {
|
||||
return false
|
||||
}
|
||||
|
||||
apis, ok := equivalentAPIs[existingAPI.PluralName]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, gv := range apis {
|
||||
if gv.Group == existingAPI.Group && gv.Version == existingAPI.Version {
|
||||
// skip exactly matched API from equivalent API list
|
||||
continue
|
||||
}
|
||||
|
||||
if gv.Group == newAPI.Group && gv.Version == newAPI.Version {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
59
vendor/sigs.k8s.io/kubefed/pkg/kubefedctl/enable/directive.go
generated
vendored
Normal file
59
vendor/sigs.k8s.io/kubefed/pkg/kubefedctl/enable/directive.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
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 enable
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"sigs.k8s.io/kubefed/pkg/kubefedctl/options"
|
||||
)
|
||||
|
||||
// EnableTypeDirectiveSpec defines the desired state of EnableTypeDirective.
|
||||
type EnableTypeDirectiveSpec struct {
|
||||
// The API version of the target type.
|
||||
// +optional
|
||||
TargetVersion string `json:"targetVersion,omitempty"`
|
||||
|
||||
// The name of the API group to use for generated federated types.
|
||||
// +optional
|
||||
FederatedGroup string `json:"federatedGroup,omitempty"`
|
||||
|
||||
// The API version to use for generated federated types.
|
||||
// +optional
|
||||
FederatedVersion string `json:"federatedVersion,omitempty"`
|
||||
}
|
||||
|
||||
// TODO(marun) This should become a proper API type and drive enabling
|
||||
// type federation via a controller. For now its only purpose is to
|
||||
// enable loading of configuration from disk.
|
||||
type EnableTypeDirective struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec EnableTypeDirectiveSpec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
func (ft *EnableTypeDirective) SetDefaults() {
|
||||
ft.Spec.FederatedGroup = options.DefaultFederatedGroup
|
||||
ft.Spec.FederatedVersion = options.DefaultFederatedVersion
|
||||
}
|
||||
|
||||
func NewEnableTypeDirective() *EnableTypeDirective {
|
||||
ft := &EnableTypeDirective{}
|
||||
ft.SetDefaults()
|
||||
return ft
|
||||
}
|
||||
428
vendor/sigs.k8s.io/kubefed/pkg/kubefedctl/enable/enable.go
generated
vendored
Normal file
428
vendor/sigs.k8s.io/kubefed/pkg/kubefedctl/enable/enable.go
generated
vendored
Normal file
@@ -0,0 +1,428 @@
|
||||
/*
|
||||
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 enable
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
apiextv1b1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
apiextv1b1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
pkgruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/klog"
|
||||
|
||||
"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
|
||||
fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
|
||||
genericclient "sigs.k8s.io/kubefed/pkg/client/generic"
|
||||
ctlutil "sigs.k8s.io/kubefed/pkg/controller/util"
|
||||
"sigs.k8s.io/kubefed/pkg/kubefedctl/options"
|
||||
"sigs.k8s.io/kubefed/pkg/kubefedctl/util"
|
||||
)
|
||||
|
||||
const (
|
||||
federatedGroupUsage = "The name of the API group to use for the generated federated type."
|
||||
targetVersionUsage = "Optional, the API version of the target type."
|
||||
)
|
||||
|
||||
var (
|
||||
enable_long = `
|
||||
Enables a Kubernetes API type (including a CRD) to be propagated
|
||||
to clusters registered with a KubeFed control plane. A CRD for
|
||||
the federated type will be generated and a FederatedTypeConfig will
|
||||
be created to configure a sync controller.
|
||||
|
||||
Current context is assumed to be a Kubernetes cluster hosting
|
||||
the kubefed control plane. Please use the
|
||||
--host-cluster-context flag otherwise.`
|
||||
|
||||
enable_example = `
|
||||
# Enable federation of Deployments
|
||||
kubefedctl enable deployments.apps --host-cluster-context=cluster1
|
||||
|
||||
# Enable federation of Deployments identified by name specified in
|
||||
# deployment.yaml
|
||||
kubefedctl enable -f deployment.yaml`
|
||||
)
|
||||
|
||||
type enableType struct {
|
||||
options.GlobalSubcommandOptions
|
||||
options.CommonEnableOptions
|
||||
enableTypeOptions
|
||||
}
|
||||
|
||||
type enableTypeOptions struct {
|
||||
federatedVersion string
|
||||
output string
|
||||
outputYAML bool
|
||||
filename string
|
||||
enableTypeDirective *EnableTypeDirective
|
||||
}
|
||||
|
||||
// Bind adds the join specific arguments to the flagset passed in as an
|
||||
// argument.
|
||||
func (o *enableTypeOptions) Bind(flags *pflag.FlagSet) {
|
||||
flags.StringVar(&o.federatedVersion, "federated-version", options.DefaultFederatedVersion, "The API version to use for the generated federated type.")
|
||||
flags.StringVarP(&o.output, "output", "o", "", "If provided, the resources that would be created in the API by the command are instead output to stdout in the provided format. Valid values are ['yaml'].")
|
||||
flags.StringVarP(&o.filename, "filename", "f", "", "If provided, the command will be configured from the provided yaml file. Only --output will be accepted from the command line")
|
||||
}
|
||||
|
||||
// NewCmdTypeEnable defines the `enable` command that
|
||||
// enables federation of a Kubernetes API type.
|
||||
func NewCmdTypeEnable(cmdOut io.Writer, config util.FedConfig) *cobra.Command {
|
||||
opts := &enableType{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "enable (NAME | -f FILENAME)",
|
||||
Short: "Enables propagation of a Kubernetes API type",
|
||||
Long: enable_long,
|
||||
Example: enable_example,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := opts.Complete(args)
|
||||
if err != nil {
|
||||
klog.Fatalf("Error: %v", err)
|
||||
}
|
||||
|
||||
err = opts.Run(cmdOut, config)
|
||||
if err != nil {
|
||||
klog.Fatalf("Error: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
opts.GlobalSubcommandBind(flags)
|
||||
opts.CommonSubcommandBind(flags, federatedGroupUsage, targetVersionUsage)
|
||||
opts.Bind(flags)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete ensures that options are valid and marshals them if necessary.
|
||||
func (j *enableType) Complete(args []string) error {
|
||||
j.enableTypeDirective = NewEnableTypeDirective()
|
||||
fd := j.enableTypeDirective
|
||||
|
||||
if j.output == "yaml" {
|
||||
j.outputYAML = true
|
||||
} else if len(j.output) > 0 {
|
||||
return errors.Errorf("Invalid value for --output: %s", j.output)
|
||||
}
|
||||
|
||||
if len(j.filename) > 0 {
|
||||
err := DecodeYAMLFromFile(j.filename, fd)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Failed to load yaml from file %q", j.filename)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := j.SetName(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fd.Name = j.TargetName
|
||||
|
||||
if len(j.TargetVersion) > 0 {
|
||||
fd.Spec.TargetVersion = j.TargetVersion
|
||||
}
|
||||
if len(j.FederatedGroup) > 0 {
|
||||
fd.Spec.FederatedGroup = j.FederatedGroup
|
||||
}
|
||||
if len(j.federatedVersion) > 0 {
|
||||
fd.Spec.FederatedVersion = j.federatedVersion
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run is the implementation of the `enable` command.
|
||||
func (j *enableType) Run(cmdOut io.Writer, config util.FedConfig) error {
|
||||
hostConfig, err := config.HostConfig(j.HostClusterContext, j.Kubeconfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to get host cluster config")
|
||||
}
|
||||
|
||||
resources, err := GetResources(hostConfig, j.enableTypeDirective)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if j.outputYAML {
|
||||
concreteTypeConfig := resources.TypeConfig.(*fedv1b1.FederatedTypeConfig)
|
||||
objects := []pkgruntime.Object{concreteTypeConfig, resources.CRD}
|
||||
err := writeObjectsToYAML(objects, cmdOut)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to write objects to YAML")
|
||||
}
|
||||
// -o yaml implies dry run
|
||||
return nil
|
||||
}
|
||||
|
||||
return CreateResources(cmdOut, hostConfig, resources, j.KubeFedNamespace, j.DryRun)
|
||||
}
|
||||
|
||||
type typeResources struct {
|
||||
TypeConfig typeconfig.Interface
|
||||
CRD *apiextv1b1.CustomResourceDefinition
|
||||
}
|
||||
|
||||
func GetResources(config *rest.Config, enableTypeDirective *EnableTypeDirective) (*typeResources, error) {
|
||||
apiResource, err := LookupAPIResource(config, enableTypeDirective.Name, enableTypeDirective.Spec.TargetVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
klog.V(2).Infof("Found type %q", resourceKey(*apiResource))
|
||||
|
||||
typeConfig := GenerateTypeConfigForTarget(*apiResource, enableTypeDirective)
|
||||
|
||||
accessor, err := newSchemaAccessor(config, *apiResource)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error initializing validation schema accessor")
|
||||
}
|
||||
|
||||
shortNames := []string{}
|
||||
for _, shortName := range apiResource.ShortNames {
|
||||
shortNames = append(shortNames, fmt.Sprintf("f%s", shortName))
|
||||
}
|
||||
|
||||
crd := federatedTypeCRD(typeConfig, accessor, shortNames)
|
||||
|
||||
return &typeResources{
|
||||
TypeConfig: typeConfig,
|
||||
CRD: crd,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO(marun) Allow updates to the configuration for a type that has
|
||||
// already been enabled for kubefed. This would likely involve
|
||||
// updating the version of the target type and the validation of the schema.
|
||||
func CreateResources(cmdOut io.Writer, config *rest.Config, resources *typeResources, namespace string, dryRun bool) error {
|
||||
write := func(data string) {
|
||||
if cmdOut != nil {
|
||||
if _, err := cmdOut.Write([]byte(data)); err != nil {
|
||||
klog.Fatalf("Unexpected err: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hostClientset, err := util.HostClientset(config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to create host clientset")
|
||||
}
|
||||
_, err = hostClientset.CoreV1().Namespaces().Get(namespace, metav1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
return errors.Wrapf(err, "KubeFed system namespace %q does not exist", namespace)
|
||||
} else if err != nil {
|
||||
return errors.Wrapf(err, "Error attempting to determine whether KubeFed system namespace %q exists", namespace)
|
||||
}
|
||||
|
||||
client, err := genericclient.New(config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to get kubefed clientset")
|
||||
}
|
||||
|
||||
concreteTypeConfig := resources.TypeConfig.(*fedv1b1.FederatedTypeConfig)
|
||||
existingTypeConfig := &fedv1b1.FederatedTypeConfig{}
|
||||
err = client.Get(context.TODO(), existingTypeConfig, namespace, concreteTypeConfig.Name)
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return errors.Wrapf(err, "Error retrieving FederatedTypeConfig %q", concreteTypeConfig.Name)
|
||||
}
|
||||
if err == nil {
|
||||
fedType := existingTypeConfig.GetFederatedType()
|
||||
target := existingTypeConfig.GetTargetType()
|
||||
concreteType := concreteTypeConfig.GetFederatedType()
|
||||
if fedType.Name != concreteType.Name || fedType.Version != concreteType.Version || fedType.Group != concreteType.Group {
|
||||
return errors.Errorf("Federation is already enabled for %q with federated type %q. Changing the federated type to %q is not supported.",
|
||||
qualifiedAPIResourceName(target),
|
||||
qualifiedAPIResourceName(fedType),
|
||||
qualifiedAPIResourceName(concreteType))
|
||||
}
|
||||
}
|
||||
|
||||
crdClient, err := apiextv1b1client.NewForConfig(config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to create crd clientset")
|
||||
}
|
||||
|
||||
existingCRD, err := crdClient.CustomResourceDefinitions().Get(resources.CRD.Name, metav1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
if !dryRun {
|
||||
_, err = crdClient.CustomResourceDefinitions().Create(resources.CRD)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error creating CRD %q", resources.CRD.Name)
|
||||
}
|
||||
}
|
||||
write(fmt.Sprintf("customresourcedefinition.apiextensions.k8s.io/%s created\n", resources.CRD.Name))
|
||||
} else if err != nil {
|
||||
return errors.Wrapf(err, "Error getting CRD %q", resources.CRD.Name)
|
||||
} else {
|
||||
ftcs := &fedv1b1.FederatedTypeConfigList{}
|
||||
err := client.List(context.TODO(), ftcs, namespace)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error getting FederatedTypeConfig list")
|
||||
}
|
||||
|
||||
for _, ftc := range ftcs.Items {
|
||||
targetAPI := concreteTypeConfig.Spec.TargetType
|
||||
existingAPI := ftc.Spec.TargetType
|
||||
if IsEquivalentAPI(&existingAPI, &targetAPI) {
|
||||
existingName := qualifiedAPIResourceName(ftc.GetTargetType())
|
||||
name := qualifiedAPIResourceName(concreteTypeConfig.GetTargetType())
|
||||
qualifiedFTCName := ctlutil.QualifiedName{
|
||||
Namespace: ftc.Namespace,
|
||||
Name: ftc.Name,
|
||||
}
|
||||
|
||||
return errors.Errorf("Failed to enable %q. Federation of this type is already enabled for equivalent type %q by FederatedTypeConfig %q",
|
||||
name, existingName, qualifiedFTCName)
|
||||
}
|
||||
|
||||
if concreteTypeConfig.Name == ftc.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
fedType := ftc.Spec.FederatedType
|
||||
name := typeconfig.GroupQualifiedName(metav1.APIResource{Name: fedType.PluralName, Group: fedType.Group})
|
||||
if name == existingCRD.Name {
|
||||
return errors.Errorf("Failed to enable federation of %q due to the FederatedTypeConfig for %q already referencing a federated type CRD named %q. If these target types are distinct despite sharing the same kind, specifying a non-default --federated-group should allow %q to be enabled.",
|
||||
concreteTypeConfig.Name, ftc.Name, name, concreteTypeConfig.Name)
|
||||
}
|
||||
}
|
||||
|
||||
existingCRD.Spec = resources.CRD.Spec
|
||||
if !dryRun {
|
||||
_, err = crdClient.CustomResourceDefinitions().Update(existingCRD)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error updating CRD %q", resources.CRD.Name)
|
||||
}
|
||||
}
|
||||
write(fmt.Sprintf("customresourcedefinition.apiextensions.k8s.io/%s updated\n", resources.CRD.Name))
|
||||
}
|
||||
|
||||
concreteTypeConfig.Namespace = namespace
|
||||
err = client.Get(context.TODO(), existingTypeConfig, namespace, concreteTypeConfig.Name)
|
||||
createdOrUpdated := "created"
|
||||
if err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return errors.Wrapf(err, "Error retrieving FederatedTypeConfig %q", concreteTypeConfig.Name)
|
||||
}
|
||||
if !dryRun {
|
||||
err = client.Create(context.TODO(), concreteTypeConfig)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error creating FederatedTypeConfig %q", concreteTypeConfig.Name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
existingTypeConfig.Spec = concreteTypeConfig.Spec
|
||||
if !dryRun {
|
||||
err = client.Update(context.TODO(), existingTypeConfig)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error updating FederatedTypeConfig %q", concreteTypeConfig.Name)
|
||||
}
|
||||
}
|
||||
createdOrUpdated = "updated"
|
||||
}
|
||||
write(fmt.Sprintf("federatedtypeconfig.core.kubefed.io/%s %s in namespace %s\n",
|
||||
concreteTypeConfig.Name, createdOrUpdated, namespace))
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateTypeConfigForTarget(apiResource metav1.APIResource, enableTypeDirective *EnableTypeDirective) typeconfig.Interface {
|
||||
spec := enableTypeDirective.Spec
|
||||
kind := apiResource.Kind
|
||||
pluralName := apiResource.Name
|
||||
typeConfig := &fedv1b1.FederatedTypeConfig{
|
||||
// Explicitly including TypeMeta will ensure it will be
|
||||
// serialized properly to yaml.
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "FederatedTypeConfig",
|
||||
APIVersion: "core.kubefed.io/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: typeconfig.GroupQualifiedName(apiResource),
|
||||
},
|
||||
Spec: fedv1b1.FederatedTypeConfigSpec{
|
||||
TargetType: fedv1b1.APIResource{
|
||||
Version: apiResource.Version,
|
||||
Kind: kind,
|
||||
Scope: NamespacedToScope(apiResource),
|
||||
},
|
||||
Propagation: fedv1b1.PropagationEnabled,
|
||||
FederatedType: fedv1b1.APIResource{
|
||||
Group: spec.FederatedGroup,
|
||||
Version: spec.FederatedVersion,
|
||||
Kind: fmt.Sprintf("Federated%s", kind),
|
||||
PluralName: fmt.Sprintf("federated%s", pluralName),
|
||||
Scope: FederatedNamespacedToScope(apiResource),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Set defaults that would normally be set by the api
|
||||
fedv1b1.SetFederatedTypeConfigDefaults(typeConfig)
|
||||
return typeConfig
|
||||
}
|
||||
|
||||
func qualifiedAPIResourceName(resource metav1.APIResource) string {
|
||||
if resource.Group == "" {
|
||||
return fmt.Sprintf("%s/%s", resource.Name, resource.Version)
|
||||
}
|
||||
return fmt.Sprintf("%s.%s/%s", resource.Name, resource.Group, resource.Version)
|
||||
}
|
||||
|
||||
func federatedTypeCRD(typeConfig typeconfig.Interface, accessor schemaAccessor, shortNames []string) *apiextv1b1.CustomResourceDefinition {
|
||||
templateSchema := accessor.templateSchema()
|
||||
schema := federatedTypeValidationSchema(templateSchema)
|
||||
return CrdForAPIResource(typeConfig.GetFederatedType(), schema, shortNames)
|
||||
}
|
||||
|
||||
func writeObjectsToYAML(objects []pkgruntime.Object, w io.Writer) error {
|
||||
for _, obj := range objects {
|
||||
if _, err := w.Write([]byte("---\n")); err != nil {
|
||||
return errors.Wrap(err, "Error encoding object to yaml")
|
||||
}
|
||||
|
||||
if err := writeObjectToYAML(obj, w); err != nil {
|
||||
return errors.Wrap(err, "Error encoding object to yaml")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeObjectToYAML(obj pkgruntime.Object, w io.Writer) error {
|
||||
json, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
unstructuredObj := &unstructured.Unstructured{}
|
||||
if _, _, err := unstructured.UnstructuredJSONScheme.Decode(json, nil, unstructuredObj); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return util.WriteUnstructuredToYaml(unstructuredObj, w)
|
||||
}
|
||||
221
vendor/sigs.k8s.io/kubefed/pkg/kubefedctl/enable/schema.go
generated
vendored
Normal file
221
vendor/sigs.k8s.io/kubefed/pkg/kubefedctl/enable/schema.go
generated
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
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 enable
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
apiextv1b1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
apiextv1b1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/kube-openapi/pkg/util/proto"
|
||||
"k8s.io/kubectl/pkg/util/openapi"
|
||||
)
|
||||
|
||||
type schemaAccessor interface {
|
||||
templateSchema() map[string]apiextv1b1.JSONSchemaProps
|
||||
}
|
||||
|
||||
func newSchemaAccessor(config *rest.Config, apiResource metav1.APIResource) (schemaAccessor, error) {
|
||||
// Assume the resource may be a CRD, and fall back to OpenAPI if that is not the case.
|
||||
crdAccessor, err := newCRDSchemaAccessor(config, apiResource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if crdAccessor != nil {
|
||||
return crdAccessor, nil
|
||||
}
|
||||
return newOpenAPISchemaAccessor(config, apiResource)
|
||||
}
|
||||
|
||||
type crdSchemaAccessor struct {
|
||||
validation *apiextv1b1.CustomResourceValidation
|
||||
}
|
||||
|
||||
func newCRDSchemaAccessor(config *rest.Config, apiResource metav1.APIResource) (schemaAccessor, error) {
|
||||
// CRDs must have a group
|
||||
if len(apiResource.Group) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
// Check whether the target resource is a crd
|
||||
crdClient, err := apiextv1b1client.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to create crd clientset")
|
||||
}
|
||||
crdName := fmt.Sprintf("%s.%s", apiResource.Name, apiResource.Group)
|
||||
crd, err := crdClient.CustomResourceDefinitions().Get(crdName, metav1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Error attempting retrieval of crd %q", crdName)
|
||||
}
|
||||
return &crdSchemaAccessor{validation: crd.Spec.Validation}, nil
|
||||
}
|
||||
|
||||
func (a *crdSchemaAccessor) templateSchema() map[string]apiextv1b1.JSONSchemaProps {
|
||||
if a.validation != nil && a.validation.OpenAPIV3Schema != nil {
|
||||
return a.validation.OpenAPIV3Schema.Properties
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type openAPISchemaAccessor struct {
|
||||
targetResource proto.Schema
|
||||
}
|
||||
|
||||
func newOpenAPISchemaAccessor(config *rest.Config, apiResource metav1.APIResource) (schemaAccessor, error) {
|
||||
client, err := discovery.NewDiscoveryClientForConfig(config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error creating discovery client")
|
||||
}
|
||||
resources, err := openapi.NewOpenAPIGetter(client).Get()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error loading openapi schema")
|
||||
}
|
||||
gvk := schema.GroupVersionKind{
|
||||
Group: apiResource.Group,
|
||||
Version: apiResource.Version,
|
||||
Kind: apiResource.Kind,
|
||||
}
|
||||
targetResource := resources.LookupResource(gvk)
|
||||
if targetResource == nil {
|
||||
return nil, errors.Errorf("Unable to find openapi schema for %q", gvk)
|
||||
}
|
||||
return &openAPISchemaAccessor{
|
||||
targetResource: targetResource,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *openAPISchemaAccessor) templateSchema() map[string]apiextv1b1.JSONSchemaProps {
|
||||
var templateSchema *apiextv1b1.JSONSchemaProps
|
||||
visitor := &jsonSchemaVistor{
|
||||
collect: func(schema apiextv1b1.JSONSchemaProps) {
|
||||
templateSchema = &schema
|
||||
},
|
||||
}
|
||||
a.targetResource.Accept(visitor)
|
||||
|
||||
return templateSchema.Properties
|
||||
}
|
||||
|
||||
// jsonSchemaVistor converts proto.Schema resources into json schema.
|
||||
// A local visitor (and associated callback) is intended to be created
|
||||
// whenever a function needs to recurse.
|
||||
//
|
||||
// TODO(marun) Generate more extensive schema if/when openapi schema
|
||||
// provides more detail as per https://github.com/ant31/crd-validation
|
||||
type jsonSchemaVistor struct {
|
||||
collect func(schema apiextv1b1.JSONSchemaProps)
|
||||
}
|
||||
|
||||
func (v *jsonSchemaVistor) VisitArray(a *proto.Array) {
|
||||
arraySchema := apiextv1b1.JSONSchemaProps{
|
||||
Type: "array",
|
||||
Items: &apiextv1b1.JSONSchemaPropsOrArray{},
|
||||
}
|
||||
localVisitor := &jsonSchemaVistor{
|
||||
collect: func(schema apiextv1b1.JSONSchemaProps) {
|
||||
arraySchema.Items.Schema = &schema
|
||||
},
|
||||
}
|
||||
a.SubType.Accept(localVisitor)
|
||||
v.collect(arraySchema)
|
||||
}
|
||||
|
||||
func (v *jsonSchemaVistor) VisitMap(m *proto.Map) {
|
||||
mapSchema := apiextv1b1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1b1.JSONSchemaPropsOrBool{
|
||||
Allows: true,
|
||||
},
|
||||
}
|
||||
localVisitor := &jsonSchemaVistor{
|
||||
collect: func(schema apiextv1b1.JSONSchemaProps) {
|
||||
mapSchema.AdditionalProperties.Schema = &schema
|
||||
},
|
||||
}
|
||||
m.SubType.Accept(localVisitor)
|
||||
v.collect(mapSchema)
|
||||
}
|
||||
|
||||
func (v *jsonSchemaVistor) VisitPrimitive(p *proto.Primitive) {
|
||||
schema := schemaForPrimitive(p)
|
||||
v.collect(schema)
|
||||
}
|
||||
|
||||
func (v *jsonSchemaVistor) VisitKind(k *proto.Kind) {
|
||||
kindSchema := apiextv1b1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Properties: make(map[string]apiextv1b1.JSONSchemaProps),
|
||||
Required: k.RequiredFields,
|
||||
}
|
||||
for key, fieldSchema := range k.Fields {
|
||||
// Status cannot be defined for a template
|
||||
if key == "status" {
|
||||
continue
|
||||
}
|
||||
localVisitor := &jsonSchemaVistor{
|
||||
collect: func(schema apiextv1b1.JSONSchemaProps) {
|
||||
kindSchema.Properties[key] = schema
|
||||
},
|
||||
}
|
||||
fieldSchema.Accept(localVisitor)
|
||||
}
|
||||
v.collect(kindSchema)
|
||||
}
|
||||
|
||||
func (v *jsonSchemaVistor) VisitReference(r proto.Reference) {
|
||||
// Short-circuit the recursive definition of JSONSchemaProps (used for CRD validation)
|
||||
//
|
||||
// TODO(marun) Implement proper support for recursive schema
|
||||
if r.Reference() == "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.JSONSchemaProps" {
|
||||
v.collect(apiextv1b1.JSONSchemaProps{Type: "object"})
|
||||
return
|
||||
}
|
||||
|
||||
r.SubSchema().Accept(v)
|
||||
}
|
||||
|
||||
func schemaForPrimitive(p *proto.Primitive) apiextv1b1.JSONSchemaProps {
|
||||
schema := apiextv1b1.JSONSchemaProps{}
|
||||
|
||||
if p.Format == "int-or-string" {
|
||||
schema.AnyOf = []apiextv1b1.JSONSchemaProps{
|
||||
{
|
||||
Type: "integer",
|
||||
Format: "int32",
|
||||
},
|
||||
{
|
||||
Type: "string",
|
||||
},
|
||||
}
|
||||
return schema
|
||||
}
|
||||
|
||||
if len(p.Format) > 0 {
|
||||
schema.Format = p.Format
|
||||
}
|
||||
schema.Type = p.Type
|
||||
return schema
|
||||
}
|
||||
199
vendor/sigs.k8s.io/kubefed/pkg/kubefedctl/enable/util.go
generated
vendored
Normal file
199
vendor/sigs.k8s.io/kubefed/pkg/kubefedctl/enable/util.go
generated
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
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 enable
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
apiextv1b1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
"sigs.k8s.io/kubefed/pkg/apis/core/common"
|
||||
"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
|
||||
)
|
||||
|
||||
func DecodeYAMLFromFile(filename string, obj interface{}) error {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return DecodeYAML(f, obj)
|
||||
}
|
||||
|
||||
func DecodeYAML(r io.Reader, obj interface{}) error {
|
||||
decoder := yaml.NewYAMLToJSONDecoder(r)
|
||||
return decoder.Decode(obj)
|
||||
}
|
||||
|
||||
func CrdForAPIResource(apiResource metav1.APIResource, validation *apiextv1b1.CustomResourceValidation, shortNames []string) *apiextv1b1.CustomResourceDefinition {
|
||||
scope := apiextv1b1.ClusterScoped
|
||||
if apiResource.Namespaced {
|
||||
scope = apiextv1b1.NamespaceScoped
|
||||
}
|
||||
return &apiextv1b1.CustomResourceDefinition{
|
||||
// Explicitly including TypeMeta will ensure it will be
|
||||
// serialized properly to yaml.
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "CustomResourceDefinition",
|
||||
APIVersion: "apiextensions.k8s.io/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: typeconfig.GroupQualifiedName(apiResource),
|
||||
},
|
||||
Spec: apiextv1b1.CustomResourceDefinitionSpec{
|
||||
Group: apiResource.Group,
|
||||
Version: apiResource.Version,
|
||||
Scope: scope,
|
||||
Names: apiextv1b1.CustomResourceDefinitionNames{
|
||||
Plural: apiResource.Name,
|
||||
Kind: apiResource.Kind,
|
||||
ShortNames: shortNames,
|
||||
},
|
||||
Validation: validation,
|
||||
Subresources: &apiextv1b1.CustomResourceSubresources{
|
||||
Status: &apiextv1b1.CustomResourceSubresourceStatus{},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func LookupAPIResource(config *rest.Config, key, targetVersion string) (*metav1.APIResource, error) {
|
||||
resourceLists, err := GetServerPreferredResources(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var targetResource *metav1.APIResource
|
||||
var matchedResources []string
|
||||
for _, resourceList := range resourceLists {
|
||||
// The list holds the GroupVersion for its list of APIResources
|
||||
gv, err := schema.ParseGroupVersion(resourceList.GroupVersion)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error parsing GroupVersion")
|
||||
}
|
||||
if len(targetVersion) > 0 && gv.Version != targetVersion {
|
||||
continue
|
||||
}
|
||||
for _, resource := range resourceList.APIResources {
|
||||
group := gv.Group
|
||||
if NameMatchesResource(key, resource, group) {
|
||||
if targetResource == nil {
|
||||
targetResource = resource.DeepCopy()
|
||||
targetResource.Group = group
|
||||
targetResource.Version = gv.Version
|
||||
}
|
||||
matchedResources = append(matchedResources, groupQualifiedName(resource.Name, gv.Group))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if len(matchedResources) > 1 {
|
||||
return nil, errors.Errorf("Multiple resources are matched by %q: %s. A group-qualified plural name must be provided.", key, strings.Join(matchedResources, ", "))
|
||||
}
|
||||
|
||||
if targetResource != nil {
|
||||
return targetResource, nil
|
||||
}
|
||||
|
||||
return nil, errors.Errorf("Unable to find api resource named %q.", key)
|
||||
}
|
||||
|
||||
func NameMatchesResource(name string, apiResource metav1.APIResource, group string) bool {
|
||||
lowerCaseName := strings.ToLower(name)
|
||||
if lowerCaseName == apiResource.Name ||
|
||||
lowerCaseName == apiResource.SingularName ||
|
||||
lowerCaseName == strings.ToLower(apiResource.Kind) ||
|
||||
lowerCaseName == fmt.Sprintf("%s.%s", apiResource.Name, group) {
|
||||
return true
|
||||
}
|
||||
for _, shortName := range apiResource.ShortNames {
|
||||
if lowerCaseName == strings.ToLower(shortName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func GetServerPreferredResources(config *rest.Config) ([]*metav1.APIResourceList, error) {
|
||||
// TODO(marun) Consider using a caching scheme ala kubectl
|
||||
client, err := discovery.NewDiscoveryClientForConfig(config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error creating discovery client")
|
||||
}
|
||||
|
||||
resourceLists, err := client.ServerPreferredResources()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error listing api resources")
|
||||
}
|
||||
return resourceLists, nil
|
||||
}
|
||||
|
||||
func NamespacedToScope(apiResource metav1.APIResource) apiextv1b1.ResourceScope {
|
||||
if apiResource.Namespaced {
|
||||
return apiextv1b1.NamespaceScoped
|
||||
}
|
||||
return apiextv1b1.ClusterScoped
|
||||
}
|
||||
|
||||
func FederatedNamespacedToScope(apiResource metav1.APIResource) apiextv1b1.ResourceScope {
|
||||
// Special-case the scope of federated namespace since it will
|
||||
// hopefully be the only instance of the scope of a federated
|
||||
// type differing from the scope of its target.
|
||||
if typeconfig.GroupQualifiedName(apiResource) == common.NamespaceName {
|
||||
// FederatedNamespace is namespaced to allow the control plane to run
|
||||
// with only namespace-scoped permissions e.g. to determine placement.
|
||||
return apiextv1b1.NamespaceScoped
|
||||
}
|
||||
return NamespacedToScope(apiResource)
|
||||
}
|
||||
|
||||
func resourceKey(apiResource metav1.APIResource) string {
|
||||
var group string
|
||||
if len(apiResource.Group) == 0 {
|
||||
group = "core"
|
||||
} else {
|
||||
group = apiResource.Group
|
||||
}
|
||||
var version string
|
||||
if len(apiResource.Version) == 0 {
|
||||
version = "v1"
|
||||
} else {
|
||||
version = apiResource.Version
|
||||
}
|
||||
return fmt.Sprintf("%s.%s/%s", apiResource.Name, group, version)
|
||||
}
|
||||
|
||||
func groupQualifiedName(name, group string) string {
|
||||
apiResource := metav1.APIResource{
|
||||
Name: name,
|
||||
Group: group,
|
||||
}
|
||||
|
||||
return typeconfig.GroupQualifiedName(apiResource)
|
||||
}
|
||||
260
vendor/sigs.k8s.io/kubefed/pkg/kubefedctl/enable/validation.go
generated
vendored
Normal file
260
vendor/sigs.k8s.io/kubefed/pkg/kubefedctl/enable/validation.go
generated
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
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 enable
|
||||
|
||||
import (
|
||||
v1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
|
||||
"sigs.k8s.io/kubefed/pkg/controller/util"
|
||||
)
|
||||
|
||||
func federatedTypeValidationSchema(templateSchema map[string]v1beta1.JSONSchemaProps) *v1beta1.CustomResourceValidation {
|
||||
schema := ValidationSchema(v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Properties: map[string]v1beta1.JSONSchemaProps{
|
||||
"placement": {
|
||||
Type: "object",
|
||||
Properties: map[string]v1beta1.JSONSchemaProps{
|
||||
// References to one or more clusters allow a
|
||||
// scheduling mechanism to explicitly indicate
|
||||
// placement. If one or more clusters is provided,
|
||||
// the clusterSelector field will be ignored.
|
||||
"clusters": {
|
||||
Type: "array",
|
||||
Items: &v1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Properties: map[string]v1beta1.JSONSchemaProps{
|
||||
"name": {
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
Required: []string{
|
||||
"name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"clusterSelector": {
|
||||
Type: "object",
|
||||
Properties: map[string]v1beta1.JSONSchemaProps{
|
||||
"matchExpressions": {
|
||||
Type: "array",
|
||||
Items: &v1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Properties: map[string]v1beta1.JSONSchemaProps{
|
||||
"key": {
|
||||
Type: "string",
|
||||
},
|
||||
"operator": {
|
||||
Type: "string",
|
||||
},
|
||||
"values": {
|
||||
Type: "array",
|
||||
Items: &v1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &v1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{
|
||||
"key",
|
||||
"operator",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"matchLabels": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &v1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &v1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"overrides": {
|
||||
Type: "array",
|
||||
Items: &v1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Properties: map[string]v1beta1.JSONSchemaProps{
|
||||
"clusterName": {
|
||||
Type: "string",
|
||||
},
|
||||
"clusterOverrides": {
|
||||
Type: "array",
|
||||
Items: &v1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Properties: map[string]v1beta1.JSONSchemaProps{
|
||||
"op": {
|
||||
Type: "string",
|
||||
Pattern: "^(add|remove|replace)?$",
|
||||
},
|
||||
"path": {
|
||||
Type: "string",
|
||||
},
|
||||
"value": {
|
||||
// Supporting the override of an arbitrary field
|
||||
// precludes up-front validation. Errors in
|
||||
// the definition of override values will need to
|
||||
// be caught during propagation.
|
||||
AnyOf: []v1beta1.JSONSchemaProps{
|
||||
{
|
||||
Type: "string",
|
||||
},
|
||||
{
|
||||
Type: "integer",
|
||||
},
|
||||
{
|
||||
Type: "boolean",
|
||||
},
|
||||
{
|
||||
Type: "object",
|
||||
},
|
||||
{
|
||||
Type: "array",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{
|
||||
"path",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if templateSchema != nil {
|
||||
specProperties := schema.OpenAPIV3Schema.Properties["spec"].Properties
|
||||
specProperties["template"] = v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
}
|
||||
// Add retainReplicas field to types that exposes a replicas
|
||||
// field that could be targeted by HPA.
|
||||
if templateSpec, ok := templateSchema["spec"]; ok {
|
||||
// TODO: find a simpler way to detect that a resource is scalable than having to compute the entire schema.
|
||||
if replicasField, ok := templateSpec.Properties["replicas"]; ok {
|
||||
if replicasField.Type == "integer" && replicasField.Format == "int32" {
|
||||
specProperties[util.RetainReplicasField] = v1beta1.JSONSchemaProps{
|
||||
Type: "boolean",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return schema
|
||||
}
|
||||
|
||||
func ValidationSchema(specProps v1beta1.JSONSchemaProps) *v1beta1.CustomResourceValidation {
|
||||
return &v1beta1.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &v1beta1.JSONSchemaProps{
|
||||
Properties: map[string]v1beta1.JSONSchemaProps{
|
||||
"apiVersion": {
|
||||
Type: "string",
|
||||
},
|
||||
"kind": {
|
||||
Type: "string",
|
||||
},
|
||||
// TODO(marun) Add a comprehensive schema for metadata
|
||||
"metadata": {
|
||||
Type: "object",
|
||||
},
|
||||
"spec": specProps,
|
||||
"status": {
|
||||
Type: "object",
|
||||
Properties: map[string]v1beta1.JSONSchemaProps{
|
||||
"conditions": {
|
||||
Type: "array",
|
||||
Items: &v1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Properties: map[string]v1beta1.JSONSchemaProps{
|
||||
"type": {
|
||||
Type: "string",
|
||||
},
|
||||
"status": {
|
||||
Type: "string",
|
||||
},
|
||||
"reason": {
|
||||
Type: "string",
|
||||
},
|
||||
"lastUpdateTime": {
|
||||
Format: "date-time",
|
||||
Type: "string",
|
||||
},
|
||||
"lastTransitionTime": {
|
||||
Format: "date-time",
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
Required: []string{
|
||||
"type",
|
||||
"status",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"clusters": {
|
||||
Type: "array",
|
||||
Items: &v1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Properties: map[string]v1beta1.JSONSchemaProps{
|
||||
"name": {
|
||||
Type: "string",
|
||||
},
|
||||
"status": {
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
Required: []string{
|
||||
"name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"observedGeneration": {
|
||||
Format: "int64",
|
||||
Type: "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Require a spec (even if empty) as an aid to users
|
||||
// manually creating federated configmaps or
|
||||
// secrets. These target types do not include a spec,
|
||||
// and the absence of the spec in a federated
|
||||
// equivalent could indicate a malformed resource.
|
||||
Required: []string{
|
||||
"spec",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user