Skip to content

Commit

Permalink
Generate mutation die helpers for fields
Browse files Browse the repository at this point in the history
Many more common mutation methods are now generated. These methods
require additional metadata since they use dies backing a field value
within the current die. Typically the required metadata is the name of
the field on the current die, and the type for the Die backing the
nested field's type. Additional metadata is required for fields backed
by slices.

See updates to the README for details.

Where possible, existing mutation methods are updated to use the new
code generation. In cases where the generated method name is different,
the previous method was deprecated in favor of the new method. Where
there is a discrepancy, the new names are more consistent.

Remaining mutation methods are mostly custom one-off methods that will
likely never be generated. Adding additional methods in the future will
be much easier as all applicable dies will get the new behavior.

Future enhancements can reduce the amount of metadata that the user must
provide for each field by looking up type information and existing doc
tags about the target field.

Signed-off-by: Scott Andrews <scott@andrews.me>
  • Loading branch information
scothis committed Jun 25, 2024
1 parent 04fcf15 commit d026610
Show file tree
Hide file tree
Showing 84 changed files with 6,773 additions and 3,165 deletions.
116 changes: 116 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- [diegen](#diegen)
- [die markers](#die-markers)
- [+die](#die)
- [+die:field](#diefield)

---

Expand Down Expand Up @@ -427,3 +428,118 @@ Properties:
- **apiVersion** `string` (optional): defaults the blanks die's APIVersion (only for objects)
- **kind** `string` (optional): defaults the blank die's Kind (only for objects)
- **ignore** `[]string` (optional): set of fields to ignore on the type

#### +die:field

```go
// +die
// +die:field:name=Selector,package=reconciler.io/dies/apis/meta/v1,die=LabelSelectorDie,pointer=true
// +die:field:name=Template,package=reconciler.io/dies/apis/core/v1,die=PodTemplateSpecDie
type MyResourceSpec struct {
Selector *metav1.LabelSelector `json:"selector`"
Template corev1.PodTemplateSpec `json:"template"`
}
```
Results in the standard die method and two additional mutation methods for the defined fields:
```go
// SelectorDie mutates Selector as a die
//
// Label selector for pods. It must match the pod template's labels.
func (d *MyResourceSpecDie) SelectorDie(fn func(d *metav1.LabelSelectorDie)) *MyResourceSpecDie {
return d.DieStamp(func(r *appsv1.DeploymentSpec) {
d := metav1.LabelSelectorBlank.DieImmutable(false).DieFeedPtr(r.Selector)
fn(d)
r.Selector = d.DieReleasePtr()
})
}
// TemplateDie mutates Template as a die
//
// Template describes the pods that will be created.
func (d *MyResourceSpecDie) TemplateDie(fn func(d *corev1.PodTemplateSpecDie)) *MyResourceSpecDie {
return d.DieStamp(func(r *appsv1.DeploymentSpec) {
d := corev1.PodTemplateSpecBlank.DieImmutable(false).DieFeed(r.Template)
fn(d)
r.Template = d.DieRelease()
})
}
```
Mutation methods for fields backed by slices require additional metadata. Depending on the content of the list it the whole listing can be replaced, or a single item in the list can be mutated.
Atomic lists cannot be mutated incrementally, the whole content is replaced. This pattern is common where there is no unique field inside the list to find a specific entry for.
```go
// +die:field:name=Ports,die=ContainerPortDie,listType=atomic
```
```go
// PortsDie replaces Ports by collecting the released value from each die passed.
func (d *ContainerDie) PortsDie(ports ...*ContainerPortDie) *ContainerDie {
...snip...
}
```
[Full source](https://pkg.go.dev/reconciler.io/dies/apis/core/v1#ContainerDie.PortsDie)
The env field is a map list type with the default key. The named env var in the list will be loaded into the EnvVarDie and passed to the callback with the mutated value updated in the slice.
Because a single item is mutated the method name uses a singular form. If the conversion from plural to singular produces a non-ideal name, the default method name can be overridden with the **method** parameter.
```go
// +die:field:name=Env,die=EnvSourceDie,listType=map
```
```go
// EnvDie mutates a single item in Env matched by the nested field Name, appending a new item if no match is found.
func (d *ContainerDie) EnvDie(name string, fn func(d *EnvVarDie)) *ContainerDie {
...snip...
}
```
[Full source](https://pkg.go.dev/reconciler.io/dies/apis/core/v1#ContainerDie.EnvDie)
The env from field is a map list type with a custom key. The default map key is `Name`.
```go
// +die:field:name=EnvFrom,die=EnvFromSourceDie,listType=map,listMapKey=Prefix
```
```go
// EnvFromDie mutates a single item in EnvFrom matched by the nested field Prefix, appending a new item if no match is found.
func (d *ContainerDie) EnvFromDie(prefix string, fn func(d *EnvFromSourceDie)) *ContainerDie {
...snip...
}
```
[Full source](https://pkg.go.dev/reconciler.io/dies/apis/core/v1#ContainerDie.EnvFromDie)
The resize policy field is also a map list type, but the map key field is defined with a custom type.
```go
// +die:field:name=ResizePolicy,die=ContainerResizePolicyDie,listType=map,listMapKey=ResourceName,listMapKeyPackage=k8s.io/api/core/v1,listMapKeyType=ResourceName
```
```go
// ResizePolicyDie mutates a single item in ResizePolicy matched by the nested field ResourceName, appending a new item if no match is found.
func (d *ContainerDie) ResizePolicyDie(resourceName corev1.ResourceName, fn func(d *ContainerResizePolicyDie)) *ContainerDie {
...snip...
}
```
[Full source](https://pkg.go.dev/reconciler.io/dies/apis/core/v1#ContainerDie.ResizePolicyDie)
Properties:
- **name** `string`: name of the field on the target resource to mutate
- **method** `string` (optional): name of the generated mutation method on the host die (defaults to the name with "Die" appended, for list type map the name is made singular)
- **package** `string` (optional): go package containing the target die and blank. `_/` is an alias to the package prefix for the built in dies, for example `_/core/v1` expands to `reconciler.io/dies/apis/core/v1`.
- **die** `string`: go type for the die backing the field's struct
- **blank** `string` (optional): go type for a blank of the die's type (defaults to the die replacing the "Die" suffix with "Blank")
- **pointer** `bool` (optional): the field is defined as a pointer reference, uses DieFeedPtr and DieReleasePtr
- **listType** `string` (optional):
- ``: field is not a list (default)
- `map`: a single value in the list is mutated, or a new item appended
- `atomic`: the list is replaced
- **listMapKey** `string` (optional): defaults to `Name` for map list types (only for list type map)
- **listMapKeyPackage** `string` (optional): the go package containing the type representing the key field on the target struct, defaults to the current package (only for list type map)
- **listMapKeyType** `string` (optional): the go type representing the key field on the target struct, defaults to `string` (only for list type map)
47 changes: 5 additions & 42 deletions apis/admission/v1/admissionrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,49 +18,12 @@ package v1

import (
admissionv1 "k8s.io/api/admission/v1"
dieauthenticationv1 "reconciler.io/dies/apis/authentication/v1"
diemetav1 "reconciler.io/dies/apis/meta/v1"
)

// +die
// +die:field:name=Kind,package=_/meta/v1,die=GroupVersionKindDie
// +die:field:name=Resource,package=_/meta/v1,die=GroupVersionResourceDie
// +die:field:name=RequestKind,package=_/meta/v1,die=GroupVersionKindDie,pointer=true
// +die:field:name=RequestResource,package=_/meta/v1,die=GroupVersionResourceDie,pointer=true
// +die:field:name=UserInfo,package=_/authentication/v1,die=UserInfoDie
type _ = admissionv1.AdmissionRequest

func (d *AdmissionRequestDie) KindDie(fn func(d *diemetav1.GroupVersionKindDie)) *AdmissionRequestDie {
return d.DieStamp(func(r *admissionv1.AdmissionRequest) {
d := diemetav1.GroupVersionKindBlank.DieImmutable(false).DieFeed(r.Kind)
fn(d)
r.Kind = d.DieRelease()
})
}

func (d *AdmissionRequestDie) ResourceDie(fn func(d *diemetav1.GroupVersionResourceDie)) *AdmissionRequestDie {
return d.DieStamp(func(r *admissionv1.AdmissionRequest) {
d := diemetav1.GroupVersionResourceBlank.DieImmutable(false).DieFeed(r.Resource)
fn(d)
r.Resource = d.DieRelease()
})
}

func (d *AdmissionRequestDie) RequestKindDie(fn func(d *diemetav1.GroupVersionKindDie)) *AdmissionRequestDie {
return d.DieStamp(func(r *admissionv1.AdmissionRequest) {
d := diemetav1.GroupVersionKindBlank.DieImmutable(false).DieFeedPtr(r.RequestKind)
fn(d)
r.RequestKind = d.DieReleasePtr()
})
}

func (d *AdmissionRequestDie) RequestResourceDie(fn func(d *diemetav1.GroupVersionResourceDie)) *AdmissionRequestDie {
return d.DieStamp(func(r *admissionv1.AdmissionRequest) {
d := diemetav1.GroupVersionResourceBlank.DieImmutable(false).DieFeedPtr(r.RequestResource)
fn(d)
r.RequestResource = d.DieReleasePtr()
})
}

func (d *AdmissionRequestDie) UserInfoDie(fn func(d *dieauthenticationv1.UserInfoDie)) *AdmissionRequestDie {
return d.DieStamp(func(r *admissionv1.AdmissionRequest) {
d := dieauthenticationv1.UserInfoBlank.DieImmutable(false).DieFeed(r.UserInfo)
fn(d)
r.UserInfo = d.DieRelease()
})
}
10 changes: 1 addition & 9 deletions apis/admission/v1/admissionresponse.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,12 @@ package v1

import (
admissionv1 "k8s.io/api/admission/v1"
diemetav1 "reconciler.io/dies/apis/meta/v1"
)

// +die
// +die:field:name=Result,package=_/meta/v1,die=StatusDie,pointer=true
type _ = admissionv1.AdmissionResponse

func (d *AdmissionResponseDie) ResultDie(fn func(d *diemetav1.StatusDie)) *AdmissionResponseDie {
return d.DieStamp(func(r *admissionv1.AdmissionResponse) {
d := diemetav1.StatusBlank.DieImmutable(false).DieFeedPtr(r.Result)
fn(d)
r.Result = d.DieReleasePtr()
})
}

func (d *AdmissionResponseDie) AddAuditAnnotation(key, value string) *AdmissionResponseDie {
return d.DieStamp(func(r *admissionv1.AdmissionResponse) {
if r.AuditAnnotations == nil {
Expand Down
18 changes: 2 additions & 16 deletions apis/admission/v1/admissionreview.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,6 @@ import (
)

// +die
// +die:field:name=Request,die=AdmissionRequestDie,pointer=true
// +die:field:name=Response,die=AdmissionResponseDie,pointer=true
type _ = admissionv1.AdmissionReview

func (d *AdmissionReviewDie) RequestDie(fn func(d *AdmissionRequestDie)) *AdmissionReviewDie {
return d.DieStamp(func(r *admissionv1.AdmissionReview) {
d := AdmissionRequestBlank.DieImmutable(false).DieFeedPtr(r.Request)
fn(d)
r.Request = d.DieReleasePtr()
})
}

func (d *AdmissionReviewDie) ResponseDie(fn func(d *AdmissionResponseDie)) *AdmissionReviewDie {
return d.DieStamp(func(r *admissionv1.AdmissionReview) {
d := AdmissionResponseBlank.DieImmutable(false).DieFeedPtr(r.Response)
fn(d)
r.Response = d.DieReleasePtr()
})
}
Loading

0 comments on commit d026610

Please sign in to comment.