open-nomad/nomad/structs/extensions.go
Tim Gross 826d9d47f9
CSI: replace structs->api with serialization extension (#12583)
The CSI HTTP API has to transform the CSI volume to redact secrets,
remove the claims fields, and to consolidate the allocation stubs into
a single slice of alloc stubs. This was done manually in #8590 but
this is a large amount of code and has proven both very bug prone
(see #8659, #8666, #8699, #8735, and #12150) and requires updating
lots of code every time we add a field to volumes or plugins.

In #10202 we introduce encoding improvements for the `Node` struct
that allow a more minimal transformation. Apply this same approach to
serializing `structs.CSIVolume` to API responses.

Also, the original reasoning behind #8590 for plugins no longer holds
because the counts are now denormalized within the state store, so we
can simply remove this transformation entirely.
2022-04-15 14:29:34 -04:00

79 lines
2.5 KiB
Go

package structs
import (
"reflect"
)
var (
// extendedTypes is a mapping of extended types to their extension function
// TODO: the duplicates could be simplified by looking up the base type in the case of a pointer type in ConvertExt
extendedTypes = map[reflect.Type]extendFunc{
reflect.TypeOf(Node{}): nodeExt,
reflect.TypeOf(&Node{}): nodeExt,
reflect.TypeOf(CSIVolume{}): csiVolumeExt,
reflect.TypeOf(&CSIVolume{}): csiVolumeExt,
}
)
// nodeExt ensures the node is sanitized and adds the legacy field .Drain back to encoded Node objects
func nodeExt(v interface{}) interface{} {
node := v.(*Node).Sanitize()
// transform to a struct with inlined Node fields plus the Drain field
// - using defined type (not an alias!) EmbeddedNode gives us free conversion to a distinct type
// - distinct type prevents this encoding extension from being called recursively/infinitely on the embedding
// - pointers mean the conversion function doesn't have to make a copy during conversion
type EmbeddedNode Node
return &struct {
*EmbeddedNode
Drain bool
}{
EmbeddedNode: (*EmbeddedNode)(node),
Drain: node != nil && node.DrainStrategy != nil,
}
}
func csiVolumeExt(v interface{}) interface{} {
vol := v.(*CSIVolume)
type EmbeddedCSIVolume CSIVolume
allocCount := len(vol.ReadAllocs) + len(vol.WriteAllocs)
apiVol := &struct {
*EmbeddedCSIVolume
Allocations []*AllocListStub
}{
EmbeddedCSIVolume: (*EmbeddedCSIVolume)(vol),
Allocations: make([]*AllocListStub, 0, allocCount),
}
// WriteAllocs and ReadAllocs will only ever contain the Allocation ID,
// with a null value for the Allocation; these IDs are mapped to
// allocation stubs in the Allocations field. This indirection is so the
// API can support both the UI and CLI consumer in a safely backwards
// compatible way
for _, a := range vol.ReadAllocs {
if a != nil {
apiVol.ReadAllocs[a.ID] = nil
apiVol.Allocations = append(apiVol.Allocations, a.Stub(nil))
}
}
for _, a := range vol.WriteAllocs {
if a != nil {
apiVol.WriteAllocs[a.ID] = nil
apiVol.Allocations = append(apiVol.Allocations, a.Stub(nil))
}
}
// MountFlags can contain secrets, so we always redact it but want
// to show the user that we have the value
if vol.MountOptions != nil && len(vol.MountOptions.MountFlags) > 0 {
apiVol.MountOptions.MountFlags = []string{"[REDACTED]"}
}
// would be better not to have at all but left in and redacted for
// backwards compatibility with the existing API
apiVol.Secrets = nil
return apiVol
}