compose/convert/placement.go

127 lines
3.4 KiB
Go

package convert
import (
"regexp"
"strings"
"github.com/compose-spec/compose-go/types"
"github.com/pkg/errors"
apiv1 "k8s.io/api/core/v1"
)
var constraintEquals = regexp.MustCompile(`([\w\.]*)\W*(==|!=)\W*([\w\.]*)`)
const (
kubernetesOs = "beta.kubernetes.io/os"
kubernetesArch = "beta.kubernetes.io/arch"
kubernetesHostname = "kubernetes.io/hostname"
)
// node.id Node ID node.id == 2ivku8v2gvtg4
// node.hostname Node hostname node.hostname != node-2
// node.role Node role node.role == manager
// node.labels user defined node labels node.labels.security == high
// engine.labels Docker Engine's labels engine.labels.operatingsystem == ubuntu 14.04
func toNodeAffinity(deploy *types.DeployConfig) (*apiv1.Affinity, error) {
constraints := []string{}
if deploy != nil && deploy.Placement.Constraints != nil {
constraints = deploy.Placement.Constraints
}
requirements := []apiv1.NodeSelectorRequirement{}
for _, constraint := range constraints {
matches := constraintEquals.FindStringSubmatch(constraint)
if len(matches) == 4 {
key := matches[1]
operator, err := toRequirementOperator(matches[2])
if err != nil {
return nil, err
}
value := matches[3]
switch {
case key == constraintOs:
requirements = append(requirements, apiv1.NodeSelectorRequirement{
Key: kubernetesOs,
Operator: operator,
Values: []string{value},
})
case key == constraintArch:
requirements = append(requirements, apiv1.NodeSelectorRequirement{
Key: kubernetesArch,
Operator: operator,
Values: []string{value},
})
case key == constraintHostname:
requirements = append(requirements, apiv1.NodeSelectorRequirement{
Key: kubernetesHostname,
Operator: operator,
Values: []string{value},
})
case strings.HasPrefix(key, constraintLabelPrefix):
requirements = append(requirements, apiv1.NodeSelectorRequirement{
Key: strings.TrimPrefix(key, constraintLabelPrefix),
Operator: operator,
Values: []string{value},
})
}
}
}
if !hasRequirement(requirements, kubernetesOs) {
requirements = append(requirements, apiv1.NodeSelectorRequirement{
Key: kubernetesOs,
Operator: apiv1.NodeSelectorOpIn,
Values: []string{"linux"},
})
}
if !hasRequirement(requirements, kubernetesArch) {
requirements = append(requirements, apiv1.NodeSelectorRequirement{
Key: kubernetesArch,
Operator: apiv1.NodeSelectorOpIn,
Values: []string{"amd64"},
})
}
return &apiv1.Affinity{
NodeAffinity: &apiv1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &apiv1.NodeSelector{
NodeSelectorTerms: []apiv1.NodeSelectorTerm{
{
MatchExpressions: requirements,
},
},
},
},
}, nil
}
const (
constraintOs = "node.platform.os"
constraintArch = "node.platform.arch"
constraintHostname = "node.hostname"
constraintLabelPrefix = "node.labels."
)
func hasRequirement(requirements []apiv1.NodeSelectorRequirement, key string) bool {
for _, r := range requirements {
if r.Key == key {
return true
}
}
return false
}
func toRequirementOperator(sign string) (apiv1.NodeSelectorOperator, error) {
switch sign {
case "==":
return apiv1.NodeSelectorOpIn, nil
case "!=":
return apiv1.NodeSelectorOpNotIn, nil
case ">":
return apiv1.NodeSelectorOpGt, nil
case "<":
return apiv1.NodeSelectorOpLt, nil
default:
return "", errors.Errorf("operator %s not supported", sign)
}
}