...

Source file src/k8s.io/apiextensions-apiserver/pkg/apihelpers/helpers.go

Documentation: k8s.io/apiextensions-apiserver/pkg/apihelpers

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package apihelpers
    18  
    19  import (
    20  	"fmt"
    21  	"net/url"
    22  	"strings"
    23  	"time"
    24  
    25  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  )
    28  
    29  // IsProtectedCommunityGroup returns whether or not a group specified for a CRD is protected for the community and needs
    30  // to have the v1beta1.KubeAPIApprovalAnnotation set.
    31  func IsProtectedCommunityGroup(group string) bool {
    32  	switch {
    33  	case group == "k8s.io" || strings.HasSuffix(group, ".k8s.io"):
    34  		return true
    35  	case group == "kubernetes.io" || strings.HasSuffix(group, ".kubernetes.io"):
    36  		return true
    37  	default:
    38  		return false
    39  	}
    40  
    41  }
    42  
    43  // APIApprovalState covers the various options for API approval annotation states
    44  type APIApprovalState int
    45  
    46  const (
    47  	// APIApprovalInvalid means the annotation doesn't have an expected value
    48  	APIApprovalInvalid APIApprovalState = iota
    49  	// APIApproved if the annotation has a URL (this means the API is approved)
    50  	APIApproved
    51  	// APIApprovalBypassed if the annotation starts with "unapproved" indicating that for whatever reason the API isn't approved, but we should allow its creation
    52  	APIApprovalBypassed
    53  	// APIApprovalMissing means the annotation is empty
    54  	APIApprovalMissing
    55  )
    56  
    57  // GetAPIApprovalState returns the state of the API approval and reason for that state
    58  func GetAPIApprovalState(annotations map[string]string) (state APIApprovalState, reason string) {
    59  	annotation := annotations[apiextensionsv1.KubeAPIApprovedAnnotation]
    60  
    61  	// we use the result of this parsing in the switch/case below
    62  	url, annotationURLParseErr := url.ParseRequestURI(annotation)
    63  	switch {
    64  	case len(annotation) == 0:
    65  		return APIApprovalMissing, fmt.Sprintf("protected groups must have approval annotation %q, see https://github.com/kubernetes/enhancements/pull/1111", apiextensionsv1.KubeAPIApprovedAnnotation)
    66  	case strings.HasPrefix(annotation, "unapproved"):
    67  		return APIApprovalBypassed, fmt.Sprintf("not approved: %q", annotation)
    68  	case annotationURLParseErr == nil && url != nil && len(url.Host) > 0 && len(url.Scheme) > 0:
    69  		return APIApproved, fmt.Sprintf("approved in %v", annotation)
    70  	default:
    71  		return APIApprovalInvalid, fmt.Sprintf("protected groups must have approval annotation %q with either a URL or a reason starting with \"unapproved\", see https://github.com/kubernetes/enhancements/pull/1111", apiextensionsv1.KubeAPIApprovedAnnotation)
    72  	}
    73  }
    74  
    75  // SetCRDCondition sets the status condition. It either overwrites the existing one or creates a new one.
    76  func SetCRDCondition(crd *apiextensionsv1.CustomResourceDefinition, newCondition apiextensionsv1.CustomResourceDefinitionCondition) {
    77  	newCondition.LastTransitionTime = metav1.NewTime(time.Now())
    78  
    79  	existingCondition := FindCRDCondition(crd, newCondition.Type)
    80  	if existingCondition == nil {
    81  		crd.Status.Conditions = append(crd.Status.Conditions, newCondition)
    82  		return
    83  	}
    84  
    85  	if existingCondition.Status != newCondition.Status || existingCondition.LastTransitionTime.IsZero() {
    86  		existingCondition.LastTransitionTime = newCondition.LastTransitionTime
    87  	}
    88  
    89  	existingCondition.Status = newCondition.Status
    90  	existingCondition.Reason = newCondition.Reason
    91  	existingCondition.Message = newCondition.Message
    92  }
    93  
    94  // RemoveCRDCondition removes the status condition.
    95  func RemoveCRDCondition(crd *apiextensionsv1.CustomResourceDefinition, conditionType apiextensionsv1.CustomResourceDefinitionConditionType) {
    96  	newConditions := []apiextensionsv1.CustomResourceDefinitionCondition{}
    97  	for _, condition := range crd.Status.Conditions {
    98  		if condition.Type != conditionType {
    99  			newConditions = append(newConditions, condition)
   100  		}
   101  	}
   102  	crd.Status.Conditions = newConditions
   103  }
   104  
   105  // FindCRDCondition returns the condition you're looking for or nil.
   106  func FindCRDCondition(crd *apiextensionsv1.CustomResourceDefinition, conditionType apiextensionsv1.CustomResourceDefinitionConditionType) *apiextensionsv1.CustomResourceDefinitionCondition {
   107  	for i := range crd.Status.Conditions {
   108  		if crd.Status.Conditions[i].Type == conditionType {
   109  			return &crd.Status.Conditions[i]
   110  		}
   111  	}
   112  
   113  	return nil
   114  }
   115  
   116  // IsCRDConditionTrue indicates if the condition is present and strictly true.
   117  func IsCRDConditionTrue(crd *apiextensionsv1.CustomResourceDefinition, conditionType apiextensionsv1.CustomResourceDefinitionConditionType) bool {
   118  	return IsCRDConditionPresentAndEqual(crd, conditionType, apiextensionsv1.ConditionTrue)
   119  }
   120  
   121  // IsCRDConditionFalse indicates if the condition is present and false.
   122  func IsCRDConditionFalse(crd *apiextensionsv1.CustomResourceDefinition, conditionType apiextensionsv1.CustomResourceDefinitionConditionType) bool {
   123  	return IsCRDConditionPresentAndEqual(crd, conditionType, apiextensionsv1.ConditionFalse)
   124  }
   125  
   126  // IsCRDConditionPresentAndEqual indicates if the condition is present and equal to the given status.
   127  func IsCRDConditionPresentAndEqual(crd *apiextensionsv1.CustomResourceDefinition, conditionType apiextensionsv1.CustomResourceDefinitionConditionType, status apiextensionsv1.ConditionStatus) bool {
   128  	for _, condition := range crd.Status.Conditions {
   129  		if condition.Type == conditionType {
   130  			return condition.Status == status
   131  		}
   132  	}
   133  	return false
   134  }
   135  
   136  // IsCRDConditionEquivalent returns true if the lhs and rhs are equivalent except for times.
   137  func IsCRDConditionEquivalent(lhs, rhs *apiextensionsv1.CustomResourceDefinitionCondition) bool {
   138  	if lhs == nil && rhs == nil {
   139  		return true
   140  	}
   141  	if lhs == nil || rhs == nil {
   142  		return false
   143  	}
   144  
   145  	return lhs.Message == rhs.Message && lhs.Reason == rhs.Reason && lhs.Status == rhs.Status && lhs.Type == rhs.Type
   146  }
   147  
   148  // CRDHasFinalizer returns true if the finalizer is in the list.
   149  func CRDHasFinalizer(crd *apiextensionsv1.CustomResourceDefinition, needle string) bool {
   150  	for _, finalizer := range crd.Finalizers {
   151  		if finalizer == needle {
   152  			return true
   153  		}
   154  	}
   155  
   156  	return false
   157  }
   158  
   159  // CRDRemoveFinalizer removes the finalizer if present.
   160  func CRDRemoveFinalizer(crd *apiextensionsv1.CustomResourceDefinition, needle string) {
   161  	newFinalizers := []string{}
   162  	for _, finalizer := range crd.Finalizers {
   163  		if finalizer != needle {
   164  			newFinalizers = append(newFinalizers, finalizer)
   165  		}
   166  	}
   167  	crd.Finalizers = newFinalizers
   168  }
   169  
   170  // HasServedCRDVersion returns true if the given version is in the list of CRD's versions and the Served flag is set.
   171  func HasServedCRDVersion(crd *apiextensionsv1.CustomResourceDefinition, version string) bool {
   172  	for _, v := range crd.Spec.Versions {
   173  		if v.Name == version {
   174  			return v.Served
   175  		}
   176  	}
   177  	return false
   178  }
   179  
   180  // GetCRDStorageVersion returns the storage version for given CRD.
   181  func GetCRDStorageVersion(crd *apiextensionsv1.CustomResourceDefinition) (string, error) {
   182  	for _, v := range crd.Spec.Versions {
   183  		if v.Storage {
   184  			return v.Name, nil
   185  		}
   186  	}
   187  	// This should not happened if crd is valid
   188  	return "", fmt.Errorf("invalid apiextensionsv1.CustomResourceDefinition, no storage version")
   189  }
   190  
   191  // IsStoredVersion returns whether the given version is the storage version of the CRD.
   192  func IsStoredVersion(crd *apiextensionsv1.CustomResourceDefinition, version string) bool {
   193  	for _, v := range crd.Status.StoredVersions {
   194  		if version == v {
   195  			return true
   196  		}
   197  	}
   198  	return false
   199  }
   200  
   201  // GetSchemaForVersion returns the validation schema for the given version or nil.
   202  func GetSchemaForVersion(crd *apiextensionsv1.CustomResourceDefinition, version string) (*apiextensionsv1.CustomResourceValidation, error) {
   203  	for _, v := range crd.Spec.Versions {
   204  		if version == v.Name {
   205  			return v.Schema, nil
   206  		}
   207  	}
   208  	return nil, fmt.Errorf("version %s not found in apiextensionsv1.CustomResourceDefinition: %v", version, crd.Name)
   209  }
   210  
   211  // GetSubresourcesForVersion returns the subresources for given version or nil.
   212  func GetSubresourcesForVersion(crd *apiextensionsv1.CustomResourceDefinition, version string) (*apiextensionsv1.CustomResourceSubresources, error) {
   213  	for _, v := range crd.Spec.Versions {
   214  		if version == v.Name {
   215  			return v.Subresources, nil
   216  		}
   217  	}
   218  	return nil, fmt.Errorf("version %s not found in apiextensionsv1.CustomResourceDefinition: %v", version, crd.Name)
   219  }
   220  
   221  // HasPerVersionSchema returns true if a CRD uses per-version schema.
   222  func HasPerVersionSchema(versions []apiextensionsv1.CustomResourceDefinitionVersion) bool {
   223  	for _, v := range versions {
   224  		if v.Schema != nil {
   225  			return true
   226  		}
   227  	}
   228  	return false
   229  }
   230  
   231  // HasPerVersionSubresources returns true if a CRD uses per-version subresources.
   232  func HasPerVersionSubresources(versions []apiextensionsv1.CustomResourceDefinitionVersion) bool {
   233  	for _, v := range versions {
   234  		if v.Subresources != nil {
   235  			return true
   236  		}
   237  	}
   238  	return false
   239  }
   240  
   241  // HasPerVersionColumns returns true if a CRD uses per-version columns.
   242  func HasPerVersionColumns(versions []apiextensionsv1.CustomResourceDefinitionVersion) bool {
   243  	for _, v := range versions {
   244  		if len(v.AdditionalPrinterColumns) > 0 {
   245  			return true
   246  		}
   247  	}
   248  	return false
   249  }
   250  
   251  // HasVersionServed returns true if given CRD has given version served.
   252  func HasVersionServed(crd *apiextensionsv1.CustomResourceDefinition, version string) bool {
   253  	for _, v := range crd.Spec.Versions {
   254  		if !v.Served || v.Name != version {
   255  			continue
   256  		}
   257  		return true
   258  	}
   259  	return false
   260  }
   261  

View as plain text