Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0b9117b

Browse files
committedDec 22, 2022
Use mdox to sync sample-adapter and doc code snippets
1 parent 3825b9c commit 0b9117b

File tree

6 files changed

+716
-18
lines changed

6 files changed

+716
-18
lines changed
 

‎Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ verify-deps:
118118

119119
.PHONY: test
120120
test:
121-
CGO_ENABLED=0 go test ./pkg/...
121+
CGO_ENABLED=0 go test ./...
122122

123123
.PHONY: test-adapter-container
124124
test-adapter-container: build-test-adapter

‎docs/getting-started.md

+16-17
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Put your provider in the `pkg/provider` directory in your repository.
3939

4040
<summary>To get started, you&#39;ll need some imports:</summary>
4141

42-
```go
42+
```go mdox-exec="go run ./hack/snippets -d package,import docs/sample-adapter/pkg/provider/provider.go"
4343
package provider
4444

4545
import (
@@ -84,7 +84,7 @@ For this walkthrough, you can just return a few statically-named metrics,
8484
two that are namespaced, and one that's on namespaces themselves, and thus
8585
root-scoped:
8686

87-
```go
87+
```go mdox-exec="go run ./hack/snippets -d func=*yourProvider.ListAllMetrics docs/sample-adapter/pkg/provider/provider.go"
8888
func (p *yourProvider) ListAllMetrics() []provider.CustomMetricInfo {
8989
return []provider.CustomMetricInfo{
9090
// these are mostly arbitrary examples
@@ -188,7 +188,7 @@ You'll need a handle to a RESTMapper (to map between resources and kinds)
188188
and dynamic client to fetch lists of objects in the cluster, if you don't
189189
already have sufficient information in your metrics pipeline:
190190

191-
```go
191+
```go mdox-exec="go run ./hack/snippets -d type=yourProvider,func=NewProvider docs/sample-adapter/pkg/provider/provider.go"
192192
type yourProvider struct {
193193
client dynamic.Interface
194194
mapper apimeta.RESTMapper
@@ -214,7 +214,7 @@ backend in these methods.
214214
First, a couple of helpers, which support doing the fake "fetch"
215215
operation, and constructing a result object:
216216

217-
```go
217+
```go mdox-exec="go run ./hack/snippets -d func=*yourProvider.valueFor,func=*yourProvider.metricFor docs/sample-adapter/pkg/provider/provider.go"
218218
// valueFor fetches a value from the fake list and increments it.
219219
func (p *yourProvider) valueFor(info provider.CustomMetricInfo) (int64, error) {
220220
// normalize the value so that you treat plural resources and singular
@@ -225,7 +225,7 @@ func (p *yourProvider) valueFor(info provider.CustomMetricInfo) (int64, error) {
225225
}
226226

227227
value := p.values[info]
228-
value += 1
228+
value++
229229
p.values[info] = value
230230

231231
return value, nil
@@ -245,7 +245,7 @@ func (p *yourProvider) metricFor(value int64, name types.NamespacedName, info pr
245245
Name: info.Metric,
246246
},
247247
// you'll want to use the actual timestamp in a real adapter
248-
Timestamp: metav1.Time{time.Now()},
248+
Timestamp: metav1.Time{Time: time.Now()},
249249
Value: *resource.NewMilliQuantity(value*100, resource.DecimalSI),
250250
}, nil
251251
}
@@ -255,7 +255,7 @@ Then, you'll need to implement the two main methods. The first fetches
255255
a single metric value for one object (for example, for the `object` metric
256256
type in the HorizontalPodAutoscaler):
257257

258-
```go
258+
```go mdox-exec="go run ./hack/snippets -d func=*yourProvider.GetMetricByName docs/sample-adapter/pkg/provider/provider.go"
259259
func (p *yourProvider) GetMetricByName(ctx context.Context, name types.NamespacedName, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValue, error) {
260260
value, err := p.valueFor(info)
261261
if err != nil {
@@ -268,7 +268,7 @@ func (p *yourProvider) GetMetricByName(ctx context.Context, name types.Namespace
268268
The second fetches multiple metric values, one for each object in a set
269269
(for example, for the `pods` metric type in the HorizontalPodAutoscaler).
270270

271-
```go
271+
```go mdox-exec="go run ./hack/snippets -d func=*yourProvider.GetMetricBySelector docs/sample-adapter/pkg/provider/provider.go"
272272
func (p *yourProvider) GetMetricBySelector(ctx context.Context, namespace string, selector labels.Selector, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValueList, error) {
273273
totalValue, err := p.valueFor(info)
274274
if err != nil {
@@ -309,11 +309,10 @@ the metrics provided by your provider.
309309

310310
<summary>First, you&#39;ll need a few imports:</summary>
311311

312-
```go
312+
```go mdox-exec="go run ./hack/snippets -d package,import docs/sample-adapter/main.go"
313313
package main
314314

315315
import (
316-
"flag"
317316
"os"
318317

319318
"k8s.io/apimachinery/pkg/util/wait"
@@ -324,7 +323,7 @@ import (
324323
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
325324

326325
// make this the path to the provider that you just wrote
327-
yourprov "example.com/youradapter/pkg/provider"
326+
yourprov "sigs.k8s.io/custom-metrics-apiserver/docs/sample-adapter/pkg/provider"
328327
)
329328
```
330329

@@ -333,7 +332,7 @@ import (
333332
With those out of the way, you can make use of the `basecmd.AdapterBase`
334333
struct to help set up the API server:
335334

336-
```go
335+
```go mdox-exec="go run ./hack/snippets -d type=YourAdapter,func=main docs/sample-adapter/main.go"
337336
type YourAdapter struct {
338337
basecmd.AdapterBase
339338

@@ -348,10 +347,10 @@ func main() {
348347
// initialize the flags, with one custom flag for the message
349348
cmd := &YourAdapter{}
350349
cmd.Flags().StringVar(&cmd.Message, "msg", "starting adapter...", "startup message")
351-
// make sure you get the klog flags
352-
logs.AddGoFlags(flag.CommandLine)
353-
cmd.Flags().AddGoFlagSet(flag.CommandLine)
354-
cmd.Flags().Parse(os.Args)
350+
logs.AddFlags(cmd.Flags())
351+
if err := cmd.Flags().Parse(os.Args); err != nil {
352+
klog.Fatalf("unable to parse flags: %v", err)
353+
}
355354

356355
provider := cmd.makeProviderOrDie()
357356
cmd.WithCustomMetrics(provider)
@@ -372,7 +371,7 @@ you might need to pass configuration for connecting to the backing metrics
372371
solution, extra credentials, or advanced configuration. For the provider
373372
you wrote above, the setup code looks something like this:
374373

375-
```go
374+
```go mdox-exec="go run ./hack/snippets -d func=*YourAdapter.makeProviderOrDie docs/sample-adapter/main.go"
376375
func (a *YourAdapter) makeProviderOrDie() provider.CustomMetricsProvider {
377376
client, err := a.DynamicClient()
378377
if err != nil {

‎docs/sample-adapter/main.go

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2022 The Kubernetes Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"os"
19+
20+
"k8s.io/apimachinery/pkg/util/wait"
21+
"k8s.io/component-base/logs"
22+
"k8s.io/klog/v2"
23+
24+
basecmd "sigs.k8s.io/custom-metrics-apiserver/pkg/cmd"
25+
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
26+
27+
// make this the path to the provider that you just wrote
28+
yourprov "sigs.k8s.io/custom-metrics-apiserver/docs/sample-adapter/pkg/provider"
29+
)
30+
31+
type YourAdapter struct {
32+
basecmd.AdapterBase
33+
34+
// the message printed on startup
35+
Message string
36+
}
37+
38+
func main() {
39+
logs.InitLogs()
40+
defer logs.FlushLogs()
41+
42+
// initialize the flags, with one custom flag for the message
43+
cmd := &YourAdapter{}
44+
cmd.Flags().StringVar(&cmd.Message, "msg", "starting adapter...", "startup message")
45+
logs.AddFlags(cmd.Flags())
46+
if err := cmd.Flags().Parse(os.Args); err != nil {
47+
klog.Fatalf("unable to parse flags: %v", err)
48+
}
49+
50+
provider := cmd.makeProviderOrDie()
51+
cmd.WithCustomMetrics(provider)
52+
// you could also set up external metrics support,
53+
// if your provider supported it:
54+
// cmd.WithExternalMetrics(provider)
55+
56+
klog.Infof(cmd.Message)
57+
if err := cmd.Run(wait.NeverStop); err != nil {
58+
klog.Fatalf("unable to run custom metrics adapter: %v", err)
59+
}
60+
}
61+
62+
func (a *YourAdapter) makeProviderOrDie() provider.CustomMetricsProvider {
63+
client, err := a.DynamicClient()
64+
if err != nil {
65+
klog.Fatalf("unable to construct dynamic client: %v", err)
66+
}
67+
68+
mapper, err := a.RESTMapper()
69+
if err != nil {
70+
klog.Fatalf("unable to construct discovery REST mapper: %v", err)
71+
}
72+
73+
return yourprov.NewProvider(client, mapper)
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Copyright 2022 The Kubernetes Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package provider
16+
17+
import (
18+
"context"
19+
"time"
20+
21+
apimeta "k8s.io/apimachinery/pkg/api/meta"
22+
"k8s.io/apimachinery/pkg/api/resource"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
"k8s.io/apimachinery/pkg/labels"
25+
"k8s.io/apimachinery/pkg/runtime/schema"
26+
"k8s.io/apimachinery/pkg/types"
27+
"k8s.io/client-go/dynamic"
28+
"k8s.io/metrics/pkg/apis/custom_metrics"
29+
30+
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
31+
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider/helpers"
32+
)
33+
34+
type yourProvider struct {
35+
client dynamic.Interface
36+
mapper apimeta.RESTMapper
37+
38+
// just increment values when they're requested
39+
values map[provider.CustomMetricInfo]int64
40+
}
41+
42+
func NewProvider(client dynamic.Interface, mapper apimeta.RESTMapper) provider.CustomMetricsProvider {
43+
return &yourProvider{
44+
client: client,
45+
mapper: mapper,
46+
values: make(map[provider.CustomMetricInfo]int64),
47+
}
48+
}
49+
50+
// valueFor fetches a value from the fake list and increments it.
51+
func (p *yourProvider) valueFor(info provider.CustomMetricInfo) (int64, error) {
52+
// normalize the value so that you treat plural resources and singular
53+
// resources the same (e.g. pods vs pod)
54+
info, _, err := info.Normalized(p.mapper)
55+
if err != nil {
56+
return 0, err
57+
}
58+
59+
value := p.values[info]
60+
value++
61+
p.values[info] = value
62+
63+
return value, nil
64+
}
65+
66+
// metricFor constructs a result for a single metric value.
67+
func (p *yourProvider) metricFor(value int64, name types.NamespacedName, info provider.CustomMetricInfo) (*custom_metrics.MetricValue, error) {
68+
// construct a reference referring to the described object
69+
objRef, err := helpers.ReferenceFor(p.mapper, name, info)
70+
if err != nil {
71+
return nil, err
72+
}
73+
74+
return &custom_metrics.MetricValue{
75+
DescribedObject: objRef,
76+
Metric: custom_metrics.MetricIdentifier{
77+
Name: info.Metric,
78+
},
79+
// you'll want to use the actual timestamp in a real adapter
80+
Timestamp: metav1.Time{Time: time.Now()},
81+
Value: *resource.NewMilliQuantity(value*100, resource.DecimalSI),
82+
}, nil
83+
}
84+
85+
func (p *yourProvider) GetMetricByName(ctx context.Context, name types.NamespacedName, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValue, error) {
86+
value, err := p.valueFor(info)
87+
if err != nil {
88+
return nil, err
89+
}
90+
return p.metricFor(value, name, info)
91+
}
92+
93+
func (p *yourProvider) GetMetricBySelector(ctx context.Context, namespace string, selector labels.Selector, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValueList, error) {
94+
totalValue, err := p.valueFor(info)
95+
if err != nil {
96+
return nil, err
97+
}
98+
99+
names, err := helpers.ListObjectNames(p.mapper, p.client, namespace, selector, info)
100+
if err != nil {
101+
return nil, err
102+
}
103+
104+
res := make([]custom_metrics.MetricValue, len(names))
105+
for i, name := range names {
106+
// in a real adapter, you might want to consider pre-computing the
107+
// object reference created in metricFor, instead of recomputing it
108+
// for each object.
109+
value, err := p.metricFor(100*totalValue/int64(len(res)), types.NamespacedName{Namespace: namespace, Name: name}, info)
110+
if err != nil {
111+
return nil, err
112+
}
113+
res[i] = *value
114+
}
115+
116+
return &custom_metrics.MetricValueList{
117+
Items: res,
118+
}, nil
119+
}
120+
121+
func (p *yourProvider) ListAllMetrics() []provider.CustomMetricInfo {
122+
return []provider.CustomMetricInfo{
123+
// these are mostly arbitrary examples
124+
{
125+
GroupResource: schema.GroupResource{Group: "", Resource: "pods"},
126+
Metric: "packets-per-second",
127+
Namespaced: true,
128+
},
129+
{
130+
GroupResource: schema.GroupResource{Group: "", Resource: "services"},
131+
Metric: "connections-per-second",
132+
Namespaced: true,
133+
},
134+
{
135+
GroupResource: schema.GroupResource{Group: "", Resource: "namespaces"},
136+
Metric: "work-queue-length",
137+
Namespaced: false,
138+
},
139+
}
140+
}

0 commit comments

Comments
 (0)
Please sign in to comment.