open-nomad/jobspec/parse_network.go
Derek Strickland 80b6f27efd
api: remove mapstructure tags fromPort struct (#12916)
This PR solves a defect in the deserialization of api.Port structs when returning structs from theEventStream.

Previously, the api.Port struct's fields were decorated with both mapstructure and hcl tags to support the network.port stanza's use of the keyword static when posting a static port value. This works fine when posting a job and when retrieving any struct that has an embedded api.Port instance as long as the value is deserialized using JSON decoding. The EventStream, however, uses mapstructure to decode event payloads in the api package. mapstructure expects an underlying field named static which does not exist. The result was that the Port.Value field would always be set to 0.

Upon further inspection, a few things became apparent.

The struct already has hcl tags that support the indirection during job submission.
Serialization/deserialization with both the json and hcl packages produce the desired result.
The use of of the mapstructure tags provided no value as the Port struct contains only fields with primitive types.
This PR:

Removes the mapstructure tags from the api.Port structs
Updates the job parsing logic to use hcl instead of mapstructure when decoding Port instances.
Closes #11044

Co-authored-by: DerekStrickland <dstrickland@hashicorp.com>
Co-authored-by: Piotr Kazmierczak <470696+pkazmierczak@users.noreply.github.com>
2022-11-08 11:26:28 +01:00

136 lines
2.9 KiB
Go

package jobspec
import (
"fmt"
"strings"
multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/nomad/api"
"github.com/mitchellh/mapstructure"
)
// ParseNetwork parses a collection containing exactly one NetworkResource
func ParseNetwork(o *ast.ObjectList) (*api.NetworkResource, error) {
if len(o.Items) > 1 {
return nil, fmt.Errorf("only one 'network' resource allowed")
}
// Check for invalid keys
valid := []string{
"mode",
"mbits",
"dns",
"port",
"hostname",
}
if err := checkHCLKeys(o.Items[0].Val, valid); err != nil {
return nil, multierror.Prefix(err, "network ->")
}
var r api.NetworkResource
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Items[0].Val); err != nil {
return nil, err
}
delete(m, "dns")
if err := mapstructure.WeakDecode(m, &r); err != nil {
return nil, err
}
var networkObj *ast.ObjectList
if ot, ok := o.Items[0].Val.(*ast.ObjectType); ok {
networkObj = ot.List
} else {
return nil, fmt.Errorf("should be an object")
}
if err := parsePorts(networkObj, &r); err != nil {
return nil, multierror.Prefix(err, "network, ports ->")
}
// Filter dns
if dns := networkObj.Filter("dns"); len(dns.Items) > 0 {
if len(dns.Items) > 1 {
return nil, multierror.Prefix(fmt.Errorf("cannot have more than 1 dns stanza"), "network ->")
}
d, err := parseDNS(dns.Items[0])
if err != nil {
return nil, multierror.Prefix(err, "network ->")
}
r.DNS = d
}
return &r, nil
}
func parsePorts(networkObj *ast.ObjectList, nw *api.NetworkResource) error {
portsObjList := networkObj.Filter("port")
knownPortLabels := make(map[string]bool)
for _, port := range portsObjList.Items {
if len(port.Keys) == 0 {
return fmt.Errorf("ports must be named")
}
// check for invalid keys
valid := []string{
"static",
"to",
"host_network",
}
if err := checkHCLKeys(port.Val, valid); err != nil {
return err
}
label := port.Keys[0].Token.Value().(string)
if !reDynamicPorts.MatchString(label) {
return errPortLabel
}
l := strings.ToLower(label)
if knownPortLabels[l] {
return fmt.Errorf("found a port label collision: %s", label)
}
var res api.Port
if err := hcl.DecodeObject(&res, port.Val); err != nil {
return err
}
res.Label = label
if res.Value > 0 {
nw.ReservedPorts = append(nw.ReservedPorts, res)
} else {
nw.DynamicPorts = append(nw.DynamicPorts, res)
}
knownPortLabels[l] = true
}
return nil
}
func parseDNS(dns *ast.ObjectItem) (*api.DNSConfig, error) {
valid := []string{
"servers",
"searches",
"options",
}
if err := checkHCLKeys(dns.Val, valid); err != nil {
return nil, multierror.Prefix(err, "dns ->")
}
var dnsCfg api.DNSConfig
var m map[string]interface{}
if err := hcl.DecodeObject(&m, dns.Val); err != nil {
return nil, err
}
if err := mapstructure.WeakDecode(m, &dnsCfg); err != nil {
return nil, err
}
return &dnsCfg, nil
}