pkg/gateway/model_build_lattice_service.go (180 lines of code) (raw):

package gateway import ( "context" "fmt" "github.com/aws/aws-sdk-go/service/vpclattice" apierrors "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" gwv1 "sigs.k8s.io/gateway-api/apis/v1" "github.com/aws/aws-application-networking-k8s/pkg/config" "github.com/aws/aws-application-networking-k8s/pkg/k8s" "github.com/aws/aws-application-networking-k8s/pkg/model/core" model "github.com/aws/aws-application-networking-k8s/pkg/model/lattice" ) //go:generate mockgen -destination model_build_lattice_service_mock.go -package gateway github.com/aws/aws-application-networking-k8s/pkg/gateway LatticeServiceBuilder type LatticeServiceBuilder interface { Build(ctx context.Context, httpRoute core.Route) (core.Stack, error) } type LatticeServiceModelBuilder struct { log gwlog.Logger client client.Client brTgBuilder BackendRefTargetGroupModelBuilder } func NewLatticeServiceBuilder( log gwlog.Logger, client client.Client, brTgBuilder BackendRefTargetGroupModelBuilder, ) *LatticeServiceModelBuilder { return &LatticeServiceModelBuilder{ log: log, client: client, brTgBuilder: brTgBuilder, } } func (b *LatticeServiceModelBuilder) Build( ctx context.Context, route core.Route, ) (core.Stack, error) { stack := core.NewDefaultStack(core.StackID(k8s.NamespacedName(route.K8sObject()))) task := &latticeServiceModelBuildTask{ log: b.log, route: route, stack: stack, client: b.client, brTgBuilder: b.brTgBuilder, } if err := task.run(ctx); err != nil { return task.stack, err } return task.stack, nil } func (t *latticeServiceModelBuildTask) run(ctx context.Context) error { err := t.buildModel(ctx) return err } func (t *latticeServiceModelBuildTask) buildModel(ctx context.Context) error { modelSvc, err := t.buildLatticeService(ctx) if err != nil { return err } err = t.buildListeners(ctx, modelSvc.ID()) if err != nil { return fmt.Errorf("failed to build listener due to %w", err) } var modelListeners []*model.Listener err = t.stack.ListResources(&modelListeners) if err != nil { return err } t.log.Debugf(ctx, "Building rules for %d listeners", len(modelListeners)) for _, modelListener := range modelListeners { if modelListener.Spec.Protocol == vpclattice.ListenerProtocolTlsPassthrough { t.log.Debugf(ctx, "Skip building rules for TLS_PASSTHROUGH listener %s, since lattice TLS_PASSTHROUGH listener can only have listener defaultAction and without any other rule", modelListener.ID()) continue } // building rules will also build target groups and targets as needed // even on delete we try to build everything we may then need to remove err = t.buildRules(ctx, modelListener.ID()) if err != nil { return fmt.Errorf("failed to build rules due to %w", err) } } return nil } func (t *latticeServiceModelBuildTask) buildLatticeService(ctx context.Context) (*model.Service, error) { var routeType core.RouteType switch t.route.(type) { case *core.HTTPRoute: routeType = core.HttpRouteType case *core.GRPCRoute: routeType = core.GrpcRouteType case *core.TLSRoute: routeType = core.TlsRouteType default: return nil, fmt.Errorf("unsupported route type: %T", t.route) } spec := model.ServiceSpec{ ServiceTagFields: model.ServiceTagFields{ RouteName: t.route.Name(), RouteNamespace: t.route.Namespace(), RouteType: routeType, }, } for _, parentRef := range t.route.Spec().ParentRefs() { gw := &gwv1.Gateway{} parentNamespace := t.route.Namespace() if parentRef.Namespace != nil { parentNamespace = string(*parentRef.Namespace) } err := t.client.Get(ctx, client.ObjectKey{Name: string(parentRef.Name), Namespace: parentNamespace}, gw) if err != nil { t.log.Infof(ctx, "Ignoring route %s because failed to get gateway %s: %v", t.route.Name(), gw.Spec.GatewayClassName, err) continue } if k8s.IsControlledByLatticeGatewayController(ctx, t.client, gw) { spec.ServiceNetworkNames = append(spec.ServiceNetworkNames, string(parentRef.Name)) } else { t.log.Infof(ctx, "Ignoring route %s because gateway %s is not managed by lattice gateway controller", t.route.Name(), gw.Name) } } if config.ServiceNetworkOverrideMode { spec.ServiceNetworkNames = []string{config.DefaultServiceNetwork} } if len(t.route.Spec().Hostnames()) > 0 { // The 1st hostname will be used as lattice customer-domain-name spec.CustomerDomainName = string(t.route.Spec().Hostnames()[0]) t.log.Infof(ctx, "Setting customer-domain-name: %s for route %s-%s", spec.CustomerDomainName, t.route.Name(), t.route.Namespace()) } else { t.log.Infof(ctx, "No custom-domain-name for route %s-%s", t.route.Name(), t.route.Namespace()) spec.CustomerDomainName = "" } certArn, err := t.getACMCertArn(ctx) if err != nil { return nil, err } spec.CustomerCertARN = certArn svc, err := model.NewLatticeService(t.stack, spec) if err != nil { return nil, err } t.log.Debugf(ctx, "Added service %s to the stack (ID %s)", svc.Spec.LatticeServiceName(), svc.ID()) svc.IsDeleted = !t.route.DeletionTimestamp().IsZero() return svc, nil } // returns empty string if not found func (t *latticeServiceModelBuildTask) getACMCertArn(ctx context.Context) (string, error) { // when a service is associate to multiple service network(s), all listener config MUST be same // so here we are only using the 1st gateway gw, err := t.findGateway(ctx) if err != nil { if apierrors.IsNotFound(err) && !t.route.DeletionTimestamp().IsZero() { return "", nil // ok if we're deleting the route } return "", err } for _, parentRef := range t.route.Spec().ParentRefs() { if string(parentRef.Name) != gw.Name { t.log.Debugf(ctx, "Ignore ParentRef of different gateway %s", parentRef.Name) continue } if parentRef.SectionName == nil { continue } for _, section := range gw.Spec.Listeners { if section.Name == *parentRef.SectionName && section.TLS != nil { if section.TLS.Mode != nil && *section.TLS.Mode == gwv1.TLSModeTerminate { curCertARN, ok := section.TLS.Options[awsCustomCertARN] if ok { t.log.Debugf(ctx, "Found certification %s under section %s", curCertARN, section.Name) return string(curCertARN), nil } } break } } } return "", nil } type latticeServiceModelBuildTask struct { log gwlog.Logger route core.Route client client.Client stack core.Stack brTgBuilder BackendRefTargetGroupModelBuilder }