1/*
2Copyright 2017 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 discovery
18
19import (
20	"net/http"
21	"sync"
22
23	restful "github.com/emicklei/go-restful"
24
25	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26	"k8s.io/apimachinery/pkg/runtime"
27	"k8s.io/apimachinery/pkg/runtime/schema"
28	utilnet "k8s.io/apimachinery/pkg/util/net"
29	"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
30	"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
31)
32
33// GroupManager is an interface that allows dynamic mutation of the existing webservice to handle
34// API groups being added or removed.
35type GroupManager interface {
36	AddGroup(apiGroup metav1.APIGroup)
37	RemoveGroup(groupName string)
38
39	WebService() *restful.WebService
40}
41
42// rootAPIsHandler creates a webservice serving api group discovery.
43// The list of APIGroups may change while the server is running because additional resources
44// are registered or removed.  It is not safe to cache the values.
45type rootAPIsHandler struct {
46	// addresses is used to build cluster IPs for discovery.
47	addresses Addresses
48
49	serializer runtime.NegotiatedSerializer
50
51	// Map storing information about all groups to be exposed in discovery response.
52	// The map is from name to the group.
53	lock      sync.RWMutex
54	apiGroups map[string]metav1.APIGroup
55	// apiGroupNames preserves insertion order
56	apiGroupNames []string
57}
58
59func NewRootAPIsHandler(addresses Addresses, serializer runtime.NegotiatedSerializer) *rootAPIsHandler {
60	// Because in release 1.1, /apis returns response with empty APIVersion, we
61	// use stripVersionNegotiatedSerializer to keep the response backwards
62	// compatible.
63	serializer = stripVersionNegotiatedSerializer{serializer}
64
65	return &rootAPIsHandler{
66		addresses:  addresses,
67		serializer: serializer,
68		apiGroups:  map[string]metav1.APIGroup{},
69	}
70}
71
72func (s *rootAPIsHandler) AddGroup(apiGroup metav1.APIGroup) {
73	s.lock.Lock()
74	defer s.lock.Unlock()
75
76	_, alreadyExists := s.apiGroups[apiGroup.Name]
77
78	s.apiGroups[apiGroup.Name] = apiGroup
79	if !alreadyExists {
80		s.apiGroupNames = append(s.apiGroupNames, apiGroup.Name)
81	}
82}
83
84func (s *rootAPIsHandler) RemoveGroup(groupName string) {
85	s.lock.Lock()
86	defer s.lock.Unlock()
87
88	delete(s.apiGroups, groupName)
89	for i := range s.apiGroupNames {
90		if s.apiGroupNames[i] == groupName {
91			s.apiGroupNames = append(s.apiGroupNames[:i], s.apiGroupNames[i+1:]...)
92			break
93		}
94	}
95}
96
97func (s *rootAPIsHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
98	s.lock.RLock()
99	defer s.lock.RUnlock()
100
101	orderedGroups := []metav1.APIGroup{}
102	for _, groupName := range s.apiGroupNames {
103		orderedGroups = append(orderedGroups, s.apiGroups[groupName])
104	}
105
106	clientIP := utilnet.GetClientIP(req)
107	serverCIDR := s.addresses.ServerAddressByClientCIDRs(clientIP)
108	groups := make([]metav1.APIGroup, len(orderedGroups))
109	for i := range orderedGroups {
110		groups[i] = orderedGroups[i]
111		groups[i].ServerAddressByClientCIDRs = serverCIDR
112	}
113
114	responsewriters.WriteObjectNegotiated(s.serializer, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, resp, req, http.StatusOK, &metav1.APIGroupList{Groups: groups})
115}
116
117func (s *rootAPIsHandler) restfulHandle(req *restful.Request, resp *restful.Response) {
118	s.ServeHTTP(resp.ResponseWriter, req.Request)
119}
120
121// WebService returns a webservice serving api group discovery.
122// Note: during the server runtime apiGroups might change.
123func (s *rootAPIsHandler) WebService() *restful.WebService {
124	mediaTypes, _ := negotiation.MediaTypesForSerializer(s.serializer)
125	ws := new(restful.WebService)
126	ws.Path(APIGroupPrefix)
127	ws.Doc("get available API versions")
128	ws.Route(ws.GET("/").To(s.restfulHandle).
129		Doc("get available API versions").
130		Operation("getAPIVersions").
131		Produces(mediaTypes...).
132		Consumes(mediaTypes...).
133		Writes(metav1.APIGroupList{}))
134	return ws
135}
136