Skip to content

Commit a5fa7b9

Browse files
authored
Merge pull request #347 from apelisse/add-list-type
✨ Add support for ListType, ListMapKeys, MapType and StructType
2 parents 2a05a0f + 6694c6b commit a5fa7b9

File tree

5 files changed

+275
-2
lines changed

5 files changed

+275
-2
lines changed

pkg/crd/markers/topology.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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 markers
18+
19+
import (
20+
"fmt"
21+
22+
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
23+
"sigs.k8s.io/controller-tools/pkg/markers"
24+
)
25+
26+
// TopologyMarkers specify topology markers (i.e. markers that describe if a
27+
// list behaves as an associative-list or a set, if a map is atomic or not).
28+
var TopologyMarkers = []*definitionWithHelp{
29+
must(markers.MakeDefinition("listMapKey", markers.DescribesField, ListMapKey(""))).
30+
WithHelp(ListMapKey("").Help()),
31+
must(markers.MakeDefinition("listType", markers.DescribesField, ListType(""))).
32+
WithHelp(ListType("").Help()),
33+
must(markers.MakeDefinition("mapType", markers.DescribesField, MapType(""))).
34+
WithHelp(MapType("").Help()),
35+
must(markers.MakeDefinition("structType", markers.DescribesField, StructType(""))).
36+
WithHelp(StructType("").Help()),
37+
}
38+
39+
func init() {
40+
AllDefinitions = append(AllDefinitions, TopologyMarkers...)
41+
}
42+
43+
// +controllertools:marker:generateHelp:category="CRD processing"
44+
45+
// ListType specifies the type of data-structure that the list
46+
// represents (map, set, atomic).
47+
//
48+
// Possible data-structure types of a list are:
49+
//
50+
// - "map": it needs to have a key field, which will be used to build an
51+
// associative list. A typical example is a the pod container list,
52+
// which is indexed by the container name.
53+
//
54+
// - "set": Fields need to be "scalar", and there can be only one
55+
// occurrence of each.
56+
//
57+
// - "atomic": All the fields in the list are treated as a single value,
58+
// are typically manipulated together by the same actor.
59+
type ListType string
60+
61+
// +controllertools:marker:generateHelp:category="CRD processing"
62+
63+
// ListMapKey specifies the keys to map listTypes.
64+
//
65+
// It indicates the index of a map list. They can be repeated if multiple keys
66+
// must be used. It can only be used when ListType is set to map, and the keys
67+
// should be scalar types.
68+
type ListMapKey string
69+
70+
// +controllertools:marker:generateHelp:category="CRD processing"
71+
72+
// MapType specifies the level of atomicity of the map;
73+
// i.e. whether each item in the map is independent of the others,
74+
// or all fields are treated as a single unit.
75+
//
76+
// Possible values:
77+
//
78+
// - "granular": items in the map are independent of each other,
79+
// and can be manipulated by different actors.
80+
// This is the default behavior.
81+
//
82+
// - "atomic": all fields are treated as one unit.
83+
// Any changes have to replace the entire map.
84+
type MapType string
85+
86+
// +controllertools:marker:generateHelp:category="CRD processing"
87+
88+
// StructType specifies the level of atomicity of the struct;
89+
// i.e. whether each field in the struct is independent of the others,
90+
// or all fields are treated as a single unit.
91+
//
92+
// Possible values:
93+
//
94+
// - "granular": fields in the struct are independent of each other,
95+
// and can be manipulated by different actors.
96+
// This is the default behavior.
97+
//
98+
// - "atomic": all fields are treated as one unit.
99+
// Any changes have to replace the entire struct.
100+
type StructType string
101+
102+
func (l ListType) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
103+
if schema.Type != "array" {
104+
return fmt.Errorf("must apply listType to an array")
105+
}
106+
if l != "map" && l != "atomic" && l != "set" {
107+
return fmt.Errorf(`ListType must be either "map", "set" or "atomic"`)
108+
}
109+
p := string(l)
110+
schema.XListType = &p
111+
return nil
112+
}
113+
114+
func (l ListType) ApplyFirst() {}
115+
116+
func (l ListMapKey) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
117+
if schema.Type != "array" {
118+
return fmt.Errorf("must apply listMapKey to an array")
119+
}
120+
if schema.XListType == nil || *schema.XListType != "map" {
121+
return fmt.Errorf("must apply listMapKey to an associative-list")
122+
}
123+
schema.XListMapKeys = append(schema.XListMapKeys, string(l))
124+
return nil
125+
}
126+
127+
func (m MapType) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
128+
if schema.Type != "object" {
129+
return fmt.Errorf("must apply mapType to an object")
130+
}
131+
132+
if m != "atomic" && m != "granular" {
133+
return fmt.Errorf(`MapType must be either "granular" or "atomic"`)
134+
}
135+
136+
p := string(m)
137+
schema.XMapType = &p
138+
139+
return nil
140+
}
141+
142+
func (s StructType) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
143+
if schema.Type != "object" && schema.Type != "" {
144+
return fmt.Errorf("must apply structType to an object; either explicitly set or defaulted through an empty schema type")
145+
}
146+
147+
if s != "atomic" && s != "granular" {
148+
return fmt.Errorf(`StructType must be either "granular" or "atomic"`)
149+
}
150+
151+
p := string(s)
152+
schema.XMapType = &p
153+
154+
return nil
155+
}

pkg/crd/markers/zz_generated.markerhelp.go

Lines changed: 46 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/crd/testdata/cronjob_types.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,20 @@ type CronJobSpec struct {
126126
// +kubebuilder:validation:EmbeddedResource
127127
// +kubebuilder:validation:nullable
128128
UnprunedEmbeddedResource runtime.RawExtension `json:"unprunedEmbeddedResource"`
129+
130+
// This tests that associative lists work.
131+
// +listType=map
132+
// +listMapKey=name
133+
// +listMapKey=secondary
134+
AssociativeList []AssociativeType `json:"associativeList"`
135+
136+
// A map that allows different actors to manage different fields
137+
// +mapType=granular
138+
MapOfInfo map[string][]byte `json:"mapOfInfo"`
139+
140+
// A struct that can only be entirely replaced
141+
// +structType=atomic
142+
StructWithSeveralFields NestedObject `json:"structWithSeveralFields"`
129143
}
130144

131145
type NestedObject struct {
@@ -137,6 +151,12 @@ type RootObject struct {
137151
Nested NestedObject `json:"nested"`
138152
}
139153

154+
type AssociativeType struct {
155+
Name string `json:"name"`
156+
Secondary int `json:"secondary"`
157+
Foo string `json:"foo"`
158+
}
159+
140160
// +kubebuilder:validation:MinLength=4
141161
// This tests that markers that are allowed on both fields and types are applied to types
142162
type LongerString string

pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,26 @@ spec:
3636
spec:
3737
description: CronJobSpec defines the desired state of CronJob
3838
properties:
39+
associativeList:
40+
description: This tests that associative lists work.
41+
items:
42+
properties:
43+
foo:
44+
type: string
45+
name:
46+
type: string
47+
secondary:
48+
type: integer
49+
required:
50+
- foo
51+
- name
52+
- secondary
53+
type: object
54+
type: array
55+
x-kubernetes-list-map-keys:
56+
- name
57+
- secondary
58+
x-kubernetes-list-type: map
3959
binaryName:
4060
description: This tests byte slice schema generation.
4161
format: byte
@@ -1602,6 +1622,10 @@ spec:
16021622
- containerPort
16031623
type: object
16041624
type: array
1625+
x-kubernetes-list-map-keys:
1626+
- containerPort
1627+
- protocol
1628+
x-kubernetes-list-type: map
16051629
readinessProbe:
16061630
description: 'Periodic probe of container service
16071631
readiness. Container will be removed from
@@ -2795,6 +2819,10 @@ spec:
27952819
- containerPort
27962820
type: object
27972821
type: array
2822+
x-kubernetes-list-map-keys:
2823+
- containerPort
2824+
- protocol
2825+
x-kubernetes-list-type: map
27982826
readinessProbe:
27992827
description: 'Periodic probe of container service
28002828
readiness. Container will be removed from
@@ -4958,6 +4986,13 @@ spec:
49584986
- template
49594987
type: object
49604988
type: object
4989+
mapOfInfo:
4990+
description: A map that allows different actors to manage different fields
4991+
type: object
4992+
x-kubernetes-map-type: granular
4993+
additionalProperties:
4994+
format: byte
4995+
type: string
49614996
noReallySuspend:
49624997
description: This flag is like suspend, but for when you really mean
49634998
it. It helps test the +kubebuilder:validation:Type marker.
@@ -4982,6 +5017,18 @@ spec:
49825017
type: array
49835018
description: This tests string slices are allowed as map values.
49845019
type: object
5020+
structWithSeveralFields:
5021+
description: A struct that can only be entirely replaced
5022+
type: object
5023+
x-kubernetes-map-type: atomic
5024+
properties:
5025+
bar:
5026+
type: boolean
5027+
foo:
5028+
type: string
5029+
required:
5030+
- bar
5031+
- foo
49855032
successfulJobsHistoryLimit:
49865033
description: The number of successful finished jobs to retain. This
49875034
is a pointer to distinguish between explicit zero and not specified.
@@ -5018,15 +5065,18 @@ spec:
50185065
type: object
50195066
x-kubernetes-preserve-unknown-fields: true
50205067
required:
5068+
- associativeList
50215069
- binaryName
50225070
- canBeNull
50235071
- defaultedObject
50245072
- defaultedSlice
50255073
- defaultedString
50265074
- embeddedResource
50275075
- jobTemplate
5076+
- mapOfInfo
50285077
- patternObject
50295078
- schedule
5079+
- structWithSeveralFields
50305080
- twoOfAKindPart0
50315081
- twoOfAKindPart1
50325082
- unprunedEmbeddedResource

pkg/crd/zz_generated.markerhelp.go

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)