pkg/model/lattice/targetgroup.go (169 lines of code) (raw):
package lattice
import (
"errors"
"fmt"
"github.com/aws/aws-sdk-go/service/vpclattice"
"github.com/aws/aws-application-networking-k8s/pkg/aws"
"github.com/aws/aws-application-networking-k8s/pkg/model/core"
"github.com/aws/aws-application-networking-k8s/pkg/utils"
)
const (
K8SClusterNameKey = aws.TagBase + "ClusterName"
K8SServiceNameKey = aws.TagBase + "ServiceName"
K8SServiceNamespaceKey = aws.TagBase + "ServiceNamespace"
K8SRouteNameKey = aws.TagBase + "RouteName"
K8SRouteNamespaceKey = aws.TagBase + "RouteNamespace"
K8SSourceTypeKey = aws.TagBase + "SourceTypeKey"
K8SProtocolVersionKey = aws.TagBase + "ProtocolVersion"
// Service specific tags
K8SRouteTypeKey = aws.TagBase + "RouteType"
MaxNamespaceLength = 55
MaxNameLength = 55
RandomSuffixLength = 10
)
type TargetGroup struct {
core.ResourceMeta `json:"-"`
Spec TargetGroupSpec `json:"spec"`
Status *TargetGroupStatus `json:"status,omitempty"`
IsDeleted bool `json:"isdeleted"`
}
type TargetGroupSpec struct {
VpcId string `json:"vpcid"`
Type TargetGroupType `json:"type"`
Port int32 `json:"port"`
Protocol string `json:"protocol"`
ProtocolVersion string `json:"protocolversion"`
IpAddressType string `json:"ipaddresstype"`
HealthCheckConfig *vpclattice.HealthCheckConfig `json:"healthcheckconfig"`
TargetGroupTagFields
}
type TargetGroupTagFields struct {
K8SClusterName string `json:"k8sclustername"`
K8SSourceType K8SSourceType `json:"k8ssourcetype"`
K8SServiceName string `json:"k8sservicename"`
K8SServiceNamespace string `json:"k8sservicenamespace"`
K8SRouteName string `json:"k8sroutename"`
K8SRouteNamespace string `json:"k8sroutenamespace"`
K8SProtocolVersion string `json:"k8sprotocolversion"`
}
type TargetGroupStatus struct {
Name string `json:"name"`
Arn string `json:"arn"`
Id string `json:"id"`
}
type TargetGroupType string
type K8SSourceType string
type RouteType string
const (
TargetGroupTypeIP TargetGroupType = "IP"
SourceTypeSvcExport K8SSourceType = "ServiceExport"
SourceTypeHTTPRoute K8SSourceType = "HTTPRoute"
SourceTypeGRPCRoute K8SSourceType = "GRPCRoute"
SourceTypeTLSRoute K8SSourceType = "TLSRoute"
SourceTypeInvalid K8SSourceType = "INVALID"
)
func TGTagFieldsFromTags(tags map[string]*string) TargetGroupTagFields {
return TargetGroupTagFields{
K8SClusterName: getMapValue(tags, K8SClusterNameKey),
K8SSourceType: GetParentRefType(getMapValue(tags, K8SSourceTypeKey)),
K8SServiceName: getMapValue(tags, K8SServiceNameKey),
K8SServiceNamespace: getMapValue(tags, K8SServiceNamespaceKey),
K8SRouteName: getMapValue(tags, K8SRouteNameKey),
K8SRouteNamespace: getMapValue(tags, K8SRouteNamespaceKey),
K8SProtocolVersion: getMapValue(tags, K8SProtocolVersionKey),
}
}
func TagsFromTGTagFields(tagFields TargetGroupTagFields) map[string]*string {
st := string(tagFields.K8SSourceType)
tags := map[string]*string{
K8SClusterNameKey: &tagFields.K8SClusterName,
K8SServiceNameKey: &tagFields.K8SServiceName,
K8SServiceNamespaceKey: &tagFields.K8SServiceNamespace,
K8SSourceTypeKey: &st,
K8SProtocolVersionKey: &tagFields.K8SProtocolVersion,
}
if tagFields.K8SSourceType != SourceTypeSvcExport {
tags[K8SRouteNameKey] = &tagFields.K8SRouteName
tags[K8SRouteNamespaceKey] = &tagFields.K8SRouteNamespace
}
return tags
}
func getMapValue(m map[string]*string, key string) string {
v, ok := m[key]
if !ok || v == nil {
return ""
}
return *v
}
func GetParentRefType(s string) K8SSourceType {
if s == "" {
return "" // empty is OK
}
switch s {
case string(SourceTypeHTTPRoute):
return SourceTypeHTTPRoute
case string(SourceTypeGRPCRoute):
return SourceTypeGRPCRoute
case string(SourceTypeTLSRoute):
return SourceTypeTLSRoute
case string(SourceTypeSvcExport):
return SourceTypeSvcExport
default:
return SourceTypeInvalid
}
}
func TagFieldsMatch(spec TargetGroupSpec, tags TargetGroupTagFields) bool {
return spec.TargetGroupTagFields == tags
}
func NewTargetGroup(stack core.Stack, spec TargetGroupSpec) (*TargetGroup, error) {
if err := spec.Validate(); err != nil {
return nil, err
}
id, err := core.IdFromHash(spec)
if err != nil {
return nil, err
}
tg := &TargetGroup{
ResourceMeta: core.NewResourceMeta(stack, "AWS:VPCServiceNetwork::TargetGroup", id),
Spec: spec,
Status: nil,
}
stack.AddResource(tg)
return tg, nil
}
func (t *TargetGroupTagFields) IsSourceTypeServiceExport() bool {
return t.K8SSourceType == SourceTypeSvcExport
}
func (t *TargetGroupTagFields) IsSourceTypeRoute() bool {
return t.K8SSourceType == SourceTypeHTTPRoute ||
t.K8SSourceType == SourceTypeGRPCRoute ||
t.K8SSourceType == SourceTypeTLSRoute
}
func (t *TargetGroupSpec) Validate() error {
requiredFields := []string{t.K8SServiceName, t.K8SServiceNamespace,
t.Protocol, t.VpcId, t.K8SClusterName, t.IpAddressType,
string(t.K8SSourceType)}
if t.Protocol != "TCP" {
requiredFields = append(requiredFields, t.ProtocolVersion)
}
for _, s := range requiredFields {
if s == "" {
return errors.New("one or more required fields are missing")
}
}
if t.IsSourceTypeRoute() {
if t.K8SRouteName == "" || t.K8SRouteNamespace == "" {
return errors.New("route name or namespace missing for route-based target group")
}
}
return nil
}
func TgNamePrefix(spec TargetGroupSpec) string {
truncSvcNamespace := utils.Truncate(spec.K8SServiceNamespace, MaxNamespaceLength)
truncSvcName := utils.Truncate(spec.K8SServiceName, MaxNameLength)
return fmt.Sprintf("k8s-%s-%s", truncSvcNamespace, truncSvcName)
}
func GenerateTgName(spec TargetGroupSpec) string {
// tg max name length 128
prefix := TgNamePrefix(spec)
randomSuffix := utils.RandomAlphaString(RandomSuffixLength)
return fmt.Sprintf("%s-%s", prefix, randomSuffix)
}