265 lines
8.2 KiB
Go
265 lines
8.2 KiB
Go
/*
|
|
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 federate
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/util/validation"
|
|
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
|
|
versionhelper "k8s.io/apimachinery/pkg/version"
|
|
"k8s.io/client-go/rest"
|
|
"k8s.io/klog"
|
|
"sigs.k8s.io/yaml"
|
|
|
|
"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
|
|
ctlutil "sigs.k8s.io/kubefed/pkg/controller/util"
|
|
"sigs.k8s.io/kubefed/pkg/kubefedctl/enable"
|
|
"sigs.k8s.io/kubefed/pkg/kubefedctl/util"
|
|
)
|
|
|
|
func RemoveUnwantedFields(resource *unstructured.Unstructured) error {
|
|
unstructured.RemoveNestedField(resource.Object, "apiVersion")
|
|
unstructured.RemoveNestedField(resource.Object, "kind")
|
|
unstructured.RemoveNestedField(resource.Object, "status")
|
|
|
|
// All metadata fields save labels should be cleared. Other
|
|
// metadata fields will be set by the system on creation or
|
|
// subsequently by controllers.
|
|
labels, _, err := unstructured.NestedMap(resource.Object, "metadata", "labels")
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to retrieve metadata.labels")
|
|
}
|
|
unstructured.RemoveNestedField(resource.Object, "metadata")
|
|
if len(labels) > 0 {
|
|
err := unstructured.SetNestedMap(resource.Object, labels, "metadata", "labels")
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to set metadata.labels")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func SetBasicMetaFields(resource *unstructured.Unstructured, apiResource metav1.APIResource, name, namespace, generateName string) {
|
|
resource.SetKind(apiResource.Kind)
|
|
gv := schema.GroupVersion{Group: apiResource.Group, Version: apiResource.Version}
|
|
resource.SetAPIVersion(gv.String())
|
|
resource.SetName(name)
|
|
if generateName != "" {
|
|
resource.SetGenerateName(generateName)
|
|
}
|
|
if apiResource.Namespaced {
|
|
resource.SetNamespace(namespace)
|
|
}
|
|
}
|
|
|
|
func namespacedAPIResourceMap(config *rest.Config, skipAPIResourceNames []string) (map[string]metav1.APIResource, error) {
|
|
apiResourceLists, err := enable.GetServerPreferredResources(config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
apiResources := make(map[string]metav1.APIResource)
|
|
for _, apiResourceList := range apiResourceLists {
|
|
if len(apiResourceList.APIResources) == 0 {
|
|
continue
|
|
}
|
|
|
|
gv, err := schema.ParseGroupVersion(apiResourceList.GroupVersion)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error parsing GroupVersion")
|
|
}
|
|
|
|
group := gv.Group
|
|
if apiResourceGroupMatchesSkipName(skipAPIResourceNames, group) {
|
|
// A whole group is skipped by the user
|
|
continue
|
|
}
|
|
|
|
if group == "extensions" {
|
|
// The strategy involved to choose a Group higher in order for k8s core
|
|
// APIs is to consider "extensions" as the outdated group [This seems to
|
|
// be true for all k8s APIResources, so far]. For example if "deployments"
|
|
// exists in "extensions" and "apps"; "deployments.apps" will be chosen.
|
|
// This doesn't apply to events but events are listed in
|
|
// controllerCreatedAPIResourceNames and so are skipped always.
|
|
|
|
// Skipping this also assumes that "extensions" is not the only
|
|
// group exposed for this resource on the API Server, which probably
|
|
// is safe as "extensions" is deprecated.
|
|
// TODO(irfanurrehman): Document this.
|
|
continue
|
|
}
|
|
|
|
for _, apiResource := range apiResourceList.APIResources {
|
|
if !apiResource.Namespaced || util.IsFederatedAPIResource(apiResource.Kind, group) ||
|
|
apiResourceMatchesSkipName(apiResource, skipAPIResourceNames, group) {
|
|
continue
|
|
}
|
|
|
|
// For all other resources (say CRDs) same kinds in different groups
|
|
// are treated as individual types. If there happens to be an API Resource
|
|
// which enables conversion and allows query of the same resource across
|
|
// different groups, a specific group resource will have to be chosen by
|
|
// the user using --skip-names to skip the not chosen one(s).
|
|
// TODO(irfanurrehman): Document this.
|
|
|
|
// The individual apiResources do not have the group and version set
|
|
apiResource.Group = group
|
|
apiResource.Version = gv.Version
|
|
groupQualifiedName := typeconfig.GroupQualifiedName(apiResource)
|
|
if previousAPIResource, ok := apiResources[groupQualifiedName]; ok {
|
|
if versionhelper.CompareKubeAwareVersionStrings(gv.Version, previousAPIResource.Version) <= 0 {
|
|
// The newer version is not latest keep the previous.
|
|
continue
|
|
}
|
|
}
|
|
|
|
apiResources[groupQualifiedName] = apiResource
|
|
}
|
|
}
|
|
|
|
return apiResources, nil
|
|
}
|
|
|
|
func apiResourceGroupMatchesSkipName(skipAPIResourceNames []string, group string) bool {
|
|
for _, name := range skipAPIResourceNames {
|
|
if name == "" {
|
|
continue
|
|
}
|
|
if name == group {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func apiResourceMatchesSkipName(apiResource metav1.APIResource, skipAPIResourceNames []string, group string) bool {
|
|
names := append(controllerCreatedAPIResourceNames, skipAPIResourceNames...)
|
|
for _, name := range names {
|
|
if name == "" {
|
|
continue
|
|
}
|
|
if enable.NameMatchesResource(name, apiResource, group) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// resources stores a list of resources for an api type
|
|
type resources struct {
|
|
// resource type information
|
|
apiResource metav1.APIResource
|
|
// resource list
|
|
resources []*unstructured.Unstructured
|
|
}
|
|
|
|
func getResourcesInNamespace(config *rest.Config, namespace string, skipAPIResourceNames []string) ([]resources, error) {
|
|
apiResources, err := namespacedAPIResourceMap(config, skipAPIResourceNames)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resourcesInNamespace := []resources{}
|
|
for _, apiResource := range apiResources {
|
|
client, err := ctlutil.NewResourceClient(config, &apiResource)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "Error creating client for %s", apiResource.Kind)
|
|
}
|
|
|
|
resourceList, err := client.Resources(namespace).List(metav1.ListOptions{})
|
|
if apierrors.IsNotFound(err) || resourceList == nil {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "Error listing resources for %s", apiResource.Kind)
|
|
}
|
|
|
|
// It would be a waste of cycles to iterate through empty slices while federating resource
|
|
if len(resourceList.Items) == 0 {
|
|
continue
|
|
}
|
|
|
|
targetResources := resources{apiResource: apiResource}
|
|
for _, item := range resourceList.Items {
|
|
resource := item
|
|
errors := validation.IsDNS1123Subdomain(resource.GetName())
|
|
if len(errors) == 0 {
|
|
targetResources.resources = append(targetResources.resources, &resource)
|
|
} else {
|
|
klog.Warningf("Skipping resource %s of type %s because it does not conform to the DNS-1123 subdomain spec.", resource.GetName(), apiResource.Name)
|
|
klog.Warningf("The following error(s) were reported during DNS-1123 validation: ")
|
|
for _, err := range errors {
|
|
klog.Warningf(err)
|
|
}
|
|
}
|
|
}
|
|
resourcesInNamespace = append(resourcesInNamespace, targetResources)
|
|
}
|
|
|
|
return resourcesInNamespace, nil
|
|
}
|
|
|
|
// decodeUnstructuredFromFile reads a list of yamls into a slice of unstructured objects
|
|
func DecodeUnstructuredFromFile(filename string) ([]*unstructured.Unstructured, error) {
|
|
var f *os.File
|
|
if filename == "-" {
|
|
f = os.Stdin
|
|
} else {
|
|
var err error
|
|
f, err = os.Open(filename)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
defer f.Close()
|
|
|
|
var unstructuredList []*unstructured.Unstructured
|
|
reader := utilyaml.NewYAMLReader(bufio.NewReader(f))
|
|
for {
|
|
unstructuedObj := &unstructured.Unstructured{}
|
|
// Read one YAML document at a time, until io.EOF is returned
|
|
buf, err := reader.Read()
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(buf) == 0 {
|
|
break
|
|
}
|
|
if err := yaml.Unmarshal(buf, unstructuedObj); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
unstructuredList = append(unstructuredList, unstructuedObj)
|
|
}
|
|
|
|
return unstructuredList, nil
|
|
}
|