1/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package helper
18
19import (
20	"sort"
21	"strings"
22
23	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24	"k8s.io/apimachinery/pkg/runtime/schema"
25	"k8s.io/apimachinery/pkg/version"
26	"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
27)
28
29// SortedByGroupAndVersion sorts APIServices into their different groups, and then sorts them based on their versions.
30// For example, the first element of the first array contains the APIService with the highest version number, in the
31// group with the highest priority; while the last element of the last array contains the APIService with the lowest
32// version number, in the group with the lowest priority.
33func SortedByGroupAndVersion(servers []*v1.APIService) [][]*v1.APIService {
34	serversByGroupPriorityMinimum := ByGroupPriorityMinimum(servers)
35	sort.Sort(serversByGroupPriorityMinimum)
36
37	ret := [][]*v1.APIService{}
38	for _, curr := range serversByGroupPriorityMinimum {
39		// check to see if we already have an entry for this group
40		existingIndex := -1
41		for j, groupInReturn := range ret {
42			if groupInReturn[0].Spec.Group == curr.Spec.Group {
43				existingIndex = j
44				break
45			}
46		}
47
48		if existingIndex >= 0 {
49			ret[existingIndex] = append(ret[existingIndex], curr)
50			sort.Sort(ByVersionPriority(ret[existingIndex]))
51			continue
52		}
53
54		ret = append(ret, []*v1.APIService{curr})
55	}
56
57	return ret
58}
59
60// ByGroupPriorityMinimum sorts with the highest group number first, then by name.
61// This is not a simple reverse, because we want the name sorting to be alpha, not
62// reverse alpha.
63type ByGroupPriorityMinimum []*v1.APIService
64
65func (s ByGroupPriorityMinimum) Len() int      { return len(s) }
66func (s ByGroupPriorityMinimum) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
67func (s ByGroupPriorityMinimum) Less(i, j int) bool {
68	if s[i].Spec.GroupPriorityMinimum != s[j].Spec.GroupPriorityMinimum {
69		return s[i].Spec.GroupPriorityMinimum > s[j].Spec.GroupPriorityMinimum
70	}
71	return s[i].Name < s[j].Name
72}
73
74// ByVersionPriority sorts with the highest version number first, then by name.
75// This is not a simple reverse, because we want the name sorting to be alpha, not
76// reverse alpha.
77type ByVersionPriority []*v1.APIService
78
79func (s ByVersionPriority) Len() int      { return len(s) }
80func (s ByVersionPriority) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
81func (s ByVersionPriority) Less(i, j int) bool {
82	if s[i].Spec.VersionPriority != s[j].Spec.VersionPriority {
83		return s[i].Spec.VersionPriority > s[j].Spec.VersionPriority
84	}
85	return version.CompareKubeAwareVersionStrings(s[i].Spec.Version, s[j].Spec.Version) > 0
86}
87
88// APIServiceNameToGroupVersion returns the GroupVersion for a given apiServiceName.  The name
89// must be valid, but any object you get back from an informer will be valid.
90func APIServiceNameToGroupVersion(apiServiceName string) schema.GroupVersion {
91	tokens := strings.SplitN(apiServiceName, ".", 2)
92	return schema.GroupVersion{Group: tokens[1], Version: tokens[0]}
93}
94
95// NewLocalAvailableAPIServiceCondition returns a condition for an available local APIService.
96func NewLocalAvailableAPIServiceCondition() v1.APIServiceCondition {
97	return v1.APIServiceCondition{
98		Type:               v1.Available,
99		Status:             v1.ConditionTrue,
100		LastTransitionTime: metav1.Now(),
101		Reason:             "Local",
102		Message:            "Local APIServices are always available",
103	}
104}
105
106// SetAPIServiceCondition sets the status condition.  It either overwrites the existing one or
107// creates a new one
108func SetAPIServiceCondition(apiService *v1.APIService, newCondition v1.APIServiceCondition) {
109	existingCondition := GetAPIServiceConditionByType(apiService, newCondition.Type)
110	if existingCondition == nil {
111		apiService.Status.Conditions = append(apiService.Status.Conditions, newCondition)
112		return
113	}
114
115	if existingCondition.Status != newCondition.Status {
116		existingCondition.Status = newCondition.Status
117		existingCondition.LastTransitionTime = newCondition.LastTransitionTime
118	}
119
120	existingCondition.Reason = newCondition.Reason
121	existingCondition.Message = newCondition.Message
122}
123
124// IsAPIServiceConditionTrue indicates if the condition is present and strictly true
125func IsAPIServiceConditionTrue(apiService *v1.APIService, conditionType v1.APIServiceConditionType) bool {
126	condition := GetAPIServiceConditionByType(apiService, conditionType)
127	return condition != nil && condition.Status == v1.ConditionTrue
128}
129
130// GetAPIServiceConditionByType gets an *APIServiceCondition by APIServiceConditionType if present
131func GetAPIServiceConditionByType(apiService *v1.APIService, conditionType v1.APIServiceConditionType) *v1.APIServiceCondition {
132	for i := range apiService.Status.Conditions {
133		if apiService.Status.Conditions[i].Type == conditionType {
134			return &apiService.Status.Conditions[i]
135		}
136	}
137	return nil
138}
139