k8s.io/client-go@v0.31.1/scale/client.go (about) 1 /* 2 Copyright 2017 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 scale 18 19 import ( 20 "context" 21 "fmt" 22 23 autoscaling "k8s.io/api/autoscaling/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/runtime" 26 "k8s.io/apimachinery/pkg/runtime/schema" 27 serializer "k8s.io/apimachinery/pkg/runtime/serializer" 28 "k8s.io/apimachinery/pkg/types" 29 "k8s.io/client-go/dynamic" 30 restclient "k8s.io/client-go/rest" 31 ) 32 33 var scaleConverter = NewScaleConverter() 34 var codecs = serializer.NewCodecFactory(scaleConverter.Scheme()) 35 var parameterScheme = runtime.NewScheme() 36 var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme) 37 38 var versionV1 = schema.GroupVersion{Version: "v1"} 39 40 func init() { 41 metav1.AddToGroupVersion(parameterScheme, versionV1) 42 } 43 44 // scaleClient is an implementation of ScalesGetter 45 // which makes use of a RESTMapper and a generic REST 46 // client to support an discoverable resource. 47 // It behaves somewhat similarly to the dynamic ClientPool, 48 // but is more specifically scoped to Scale. 49 type scaleClient struct { 50 mapper PreferredResourceMapper 51 52 apiPathResolverFunc dynamic.APIPathResolverFunc 53 scaleKindResolver ScaleKindResolver 54 clientBase restclient.Interface 55 } 56 57 // NewForConfig creates a new ScalesGetter which resolves kinds 58 // to resources using the given RESTMapper, and API paths using 59 // the given dynamic.APIPathResolverFunc. 60 func NewForConfig(cfg *restclient.Config, mapper PreferredResourceMapper, resolver dynamic.APIPathResolverFunc, scaleKindResolver ScaleKindResolver) (ScalesGetter, error) { 61 // so that the RESTClientFor doesn't complain 62 cfg.GroupVersion = &schema.GroupVersion{} 63 64 cfg.NegotiatedSerializer = codecs.WithoutConversion() 65 if len(cfg.UserAgent) == 0 { 66 cfg.UserAgent = restclient.DefaultKubernetesUserAgent() 67 } 68 69 client, err := restclient.RESTClientFor(cfg) 70 if err != nil { 71 return nil, err 72 } 73 74 return New(client, mapper, resolver, scaleKindResolver), nil 75 } 76 77 // New creates a new ScalesGetter using the given client to make requests. 78 // The GroupVersion on the client is ignored. 79 func New(baseClient restclient.Interface, mapper PreferredResourceMapper, resolver dynamic.APIPathResolverFunc, scaleKindResolver ScaleKindResolver) ScalesGetter { 80 return &scaleClient{ 81 mapper: mapper, 82 83 apiPathResolverFunc: resolver, 84 scaleKindResolver: scaleKindResolver, 85 clientBase: baseClient, 86 } 87 } 88 89 // apiPathFor returns the absolute api path for the given GroupVersion 90 func (c *scaleClient) apiPathFor(groupVer schema.GroupVersion) string { 91 // we need to set the API path based on GroupVersion (defaulting to the legacy path if none is set) 92 // TODO: we "cheat" here since the API path really only depends on group ATM, but this should 93 // *probably* take GroupVersionResource and not GroupVersionKind. 94 apiPath := c.apiPathResolverFunc(groupVer.WithKind("")) 95 if apiPath == "" { 96 apiPath = "/api" 97 } 98 99 return restclient.DefaultVersionedAPIPath(apiPath, groupVer) 100 } 101 102 // pathAndVersionFor returns the appropriate base path and the associated full GroupVersionResource 103 // for the given GroupResource 104 func (c *scaleClient) pathAndVersionFor(resource schema.GroupResource) (string, schema.GroupVersionResource, error) { 105 gvr, err := c.mapper.ResourceFor(resource.WithVersion("")) 106 if err != nil { 107 return "", gvr, fmt.Errorf("unable to get full preferred group-version-resource for %s: %v", resource.String(), err) 108 } 109 110 groupVer := gvr.GroupVersion() 111 112 return c.apiPathFor(groupVer), gvr, nil 113 } 114 115 // namespacedScaleClient is an ScaleInterface for fetching 116 // Scales in a given namespace. 117 type namespacedScaleClient struct { 118 client *scaleClient 119 namespace string 120 } 121 122 // convertToScale converts the response body to autoscaling/v1.Scale 123 func convertToScale(result *restclient.Result) (*autoscaling.Scale, error) { 124 scaleBytes, err := result.Raw() 125 if err != nil { 126 return nil, err 127 } 128 decoder := scaleConverter.codecs.UniversalDecoder(scaleConverter.ScaleVersions()...) 129 rawScaleObj, err := runtime.Decode(decoder, scaleBytes) 130 if err != nil { 131 return nil, err 132 } 133 134 // convert whatever this is to autoscaling/v1.Scale 135 scaleObj, err := scaleConverter.ConvertToVersion(rawScaleObj, autoscaling.SchemeGroupVersion) 136 if err != nil { 137 return nil, fmt.Errorf("received an object from a /scale endpoint which was not convertible to autoscaling Scale: %v", err) 138 } 139 140 return scaleObj.(*autoscaling.Scale), nil 141 } 142 143 func (c *scaleClient) Scales(namespace string) ScaleInterface { 144 return &namespacedScaleClient{ 145 client: c, 146 namespace: namespace, 147 } 148 } 149 150 func (c *namespacedScaleClient) Get(ctx context.Context, resource schema.GroupResource, name string, opts metav1.GetOptions) (*autoscaling.Scale, error) { 151 // Currently, a /scale endpoint can return different scale types. 152 // Until we have support for the alternative API representations proposal, 153 // we need to deal with accepting different API versions. 154 // In practice, this is autoscaling/v1.Scale and extensions/v1beta1.Scale 155 156 path, gvr, err := c.client.pathAndVersionFor(resource) 157 if err != nil { 158 return nil, fmt.Errorf("unable to get client for %s: %v", resource.String(), err) 159 } 160 161 result := c.client.clientBase.Get(). 162 AbsPath(path). 163 NamespaceIfScoped(c.namespace, c.namespace != ""). 164 Resource(gvr.Resource). 165 Name(name). 166 SubResource("scale"). 167 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1). 168 Do(ctx) 169 if err := result.Error(); err != nil { 170 return nil, err 171 } 172 173 return convertToScale(&result) 174 } 175 176 func (c *namespacedScaleClient) Update(ctx context.Context, resource schema.GroupResource, scale *autoscaling.Scale, opts metav1.UpdateOptions) (*autoscaling.Scale, error) { 177 path, gvr, err := c.client.pathAndVersionFor(resource) 178 if err != nil { 179 return nil, fmt.Errorf("unable to get client for %s: %v", resource.String(), err) 180 } 181 182 // Currently, a /scale endpoint can receive and return different scale types. 183 // Until we have support for the alternative API representations proposal, 184 // we need to deal with sending and accepting different API versions. 185 186 // figure out what scale we actually need here 187 desiredGVK, err := c.client.scaleKindResolver.ScaleForResource(gvr) 188 if err != nil { 189 return nil, fmt.Errorf("could not find proper group-version for scale subresource of %s: %v", gvr.String(), err) 190 } 191 192 // convert this to whatever this endpoint wants 193 scaleUpdate, err := scaleConverter.ConvertToVersion(scale, desiredGVK.GroupVersion()) 194 if err != nil { 195 return nil, fmt.Errorf("could not convert scale update to external Scale: %v", err) 196 } 197 encoder := scaleConverter.codecs.LegacyCodec(desiredGVK.GroupVersion()) 198 scaleUpdateBytes, err := runtime.Encode(encoder, scaleUpdate) 199 if err != nil { 200 return nil, fmt.Errorf("could not encode scale update to external Scale: %v", err) 201 } 202 203 result := c.client.clientBase.Put(). 204 AbsPath(path). 205 NamespaceIfScoped(c.namespace, c.namespace != ""). 206 Resource(gvr.Resource). 207 Name(scale.Name). 208 SubResource("scale"). 209 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1). 210 Body(scaleUpdateBytes). 211 Do(ctx) 212 if err := result.Error(); err != nil { 213 // propagate "raw" error from the API 214 // this allows callers to interpret underlying Reason field 215 // for example: errors.IsConflict(err) 216 return nil, err 217 } 218 219 return convertToScale(&result) 220 } 221 222 func (c *namespacedScaleClient) Patch(ctx context.Context, gvr schema.GroupVersionResource, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions) (*autoscaling.Scale, error) { 223 groupVersion := gvr.GroupVersion() 224 result := c.client.clientBase.Patch(pt). 225 AbsPath(c.client.apiPathFor(groupVersion)). 226 NamespaceIfScoped(c.namespace, c.namespace != ""). 227 Resource(gvr.Resource). 228 Name(name). 229 SubResource("scale"). 230 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1). 231 Body(data). 232 Do(ctx) 233 if err := result.Error(); err != nil { 234 return nil, err 235 } 236 237 return convertToScale(&result) 238 }