Skip to content

Commit 99c4255

Browse files
authored
change FindServiceNetwork algo, use name and id match (#566)
* change FindServiceNetwork algo, use name and id match
1 parent 771efb5 commit 99c4255

15 files changed

+298
-366
lines changed

pkg/aws/cloud.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func NewCloud(log gwlog.Logger, cfg CloudConfig) (Cloud, error) {
7070
}
7171
})
7272

73-
lattice := services.NewDefaultLattice(sess, cfg.Region)
73+
lattice := services.NewDefaultLattice(sess, cfg.AccountId, cfg.Region)
7474
tagging := services.NewDefaultTagging(sess, cfg.Region)
7575
cl := NewDefaultCloudWithTagging(lattice, tagging, cfg)
7676
return cl, nil

pkg/aws/services/vpclattice.go

Lines changed: 93 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,38 @@ import (
55
"errors"
66
"fmt"
77
"os"
8+
"strings"
89
"time"
910

11+
"github.com/aws/aws-sdk-go/aws/arn"
1012
"github.com/aws/aws-sdk-go/aws/awserr"
1113
"github.com/hashicorp/golang-lru/v2/expirable"
1214

1315
"github.com/aws/aws-sdk-go/aws"
14-
"github.com/aws/aws-sdk-go/aws/arn"
1516
"github.com/aws/aws-sdk-go/aws/request"
1617
"github.com/aws/aws-sdk-go/aws/session"
1718
"github.com/aws/aws-sdk-go/service/vpclattice"
1819
"github.com/aws/aws-sdk-go/service/vpclattice/vpclatticeiface"
1920

2021
"github.com/aws/aws-application-networking-k8s/pkg/config"
22+
"github.com/aws/aws-application-networking-k8s/pkg/utils"
2123
)
2224

2325
//go:generate mockgen -destination vpclattice_mocks.go -package services github.com/aws/aws-application-networking-k8s/pkg/aws/services Lattice
2426

27+
var (
28+
ErrNameConflict = errors.New("name conflict")
29+
ErrNotFound = errors.New("not found")
30+
ErrInternal = errors.New("internal error")
31+
)
32+
2533
type ServiceNetworkInfo struct {
2634
SvcNetwork vpclattice.ServiceNetworkSummary
2735
Tags Tags
2836
}
2937

30-
type NotFoundError struct {
31-
ResourceType string
32-
Name string
33-
}
34-
35-
func (e *NotFoundError) Error() string {
36-
return fmt.Sprintf("%s %s not found", e.ResourceType, e.Name)
37-
}
38-
3938
func NewNotFoundError(resourceType string, name string) error {
40-
return &NotFoundError{resourceType, name}
39+
return fmt.Errorf("%w, %s %s", ErrNotFound, resourceType, name)
4140
}
4241

4342
func IsNotFoundError(err error) bool {
@@ -46,8 +45,7 @@ func IsNotFoundError(err error) bool {
4645
return true
4746
}
4847
}
49-
nfErr := &NotFoundError{}
50-
return errors.As(err, &nfErr)
48+
return errors.Is(err, ErrNotFound)
5149
}
5250

5351
func IgnoreNotFound(err error) error {
@@ -104,17 +102,17 @@ type Lattice interface {
104102
ListTargetsAsList(ctx context.Context, input *vpclattice.ListTargetsInput) ([]*vpclattice.TargetSummary, error)
105103
ListServiceNetworkVpcAssociationsAsList(ctx context.Context, input *vpclattice.ListServiceNetworkVpcAssociationsInput) ([]*vpclattice.ServiceNetworkVpcAssociationSummary, error)
106104
ListServiceNetworkServiceAssociationsAsList(ctx context.Context, input *vpclattice.ListServiceNetworkServiceAssociationsInput) ([]*vpclattice.ServiceNetworkServiceAssociationSummary, error)
107-
FindServiceNetwork(ctx context.Context, name string, accountId string) (*ServiceNetworkInfo, error)
105+
FindServiceNetwork(ctx context.Context, nameOrId string) (*ServiceNetworkInfo, error)
108106
FindService(ctx context.Context, latticeServiceName string) (*vpclattice.ServiceSummary, error)
109107
}
110108

111109
type defaultLattice struct {
112110
vpclatticeiface.VPCLatticeAPI
113-
114-
cache *expirable.LRU[string, any]
111+
ownAccount string
112+
cache *expirable.LRU[string, any]
115113
}
116114

117-
func NewDefaultLattice(sess *session.Session, region string) *defaultLattice {
115+
func NewDefaultLattice(sess *session.Session, acc string, region string) *defaultLattice {
118116

119117
latticeEndpoint := "https://vpc-lattice." + region + ".amazonaws.com"
120118
endpoint := os.Getenv("LATTICE_ENDPOINT")
@@ -127,7 +125,11 @@ func NewDefaultLattice(sess *session.Session, region string) *defaultLattice {
127125

128126
cache := expirable.NewLRU[string, any](1000, nil, time.Second*60)
129127

130-
return &defaultLattice{VPCLatticeAPI: latticeSess, cache: cache}
128+
return &defaultLattice{
129+
VPCLatticeAPI: latticeSess,
130+
ownAccount: acc,
131+
cache: cache,
132+
}
131133
}
132134

133135
func (d *defaultLattice) ListListenersAsList(ctx context.Context, input *vpclattice.ListListenersInput) ([]*vpclattice.ListenerSummary, error) {
@@ -313,60 +315,92 @@ func (d *defaultLattice) ListServiceNetworkServiceAssociationsAsList(ctx context
313315
return result, nil
314316
}
315317

316-
func (d *defaultLattice) FindServiceNetwork(ctx context.Context, name string, optionalAccountId string) (*ServiceNetworkInfo, error) {
317-
// When default service network is provided, override for any kind of SN search
318-
if config.ServiceNetworkOverrideMode {
319-
name = config.DefaultServiceNetwork
318+
func (d *defaultLattice) snSummaryToLog(snSum []*vpclattice.ServiceNetworkSummary) string {
319+
out := make([]string, len(snSum))
320+
for i, s := range snSum {
321+
out[i] = fmt.Sprintf("{name=%s, id=%s}", aws.StringValue(s.Name), aws.StringValue(s.Id))
320322
}
321-
input := vpclattice.ListServiceNetworksInput{}
323+
return strings.Join(out, ",")
324+
}
322325

323-
var innerErr error
324-
var snMatch *ServiceNetworkInfo
325-
err := d.ListServiceNetworksPagesWithContext(ctx, &input, func(page *vpclattice.ListServiceNetworksOutput, lastPage bool) bool {
326-
for _, r := range page.Items {
327-
if aws.StringValue(r.Name) != name {
328-
continue
329-
}
330-
acctIdMatches, err1 := accountIdMatches(optionalAccountId, *r.Arn)
331-
if err1 != nil {
332-
innerErr = err1
333-
return false
334-
}
335-
if !acctIdMatches {
336-
continue
337-
}
326+
// Try find by name first, if there is no single match, continue with id match. Ideally name match
327+
// should work just fine, but in desperate scenario of shared SN when naming collision happens using
328+
// id can be an option
329+
func (d *defaultLattice) serviceNetworkMatch(allSn []*vpclattice.ServiceNetworkSummary, nameOrId string) (*vpclattice.ServiceNetworkSummary, error) {
330+
var snMatch *vpclattice.ServiceNetworkSummary
331+
nameMatch := utils.SliceFilter(allSn, func(snSum *vpclattice.ServiceNetworkSummary) bool {
332+
return aws.StringValue(snSum.Name) == nameOrId
333+
})
334+
idMatch := utils.SliceFilter(allSn, func(snSum *vpclattice.ServiceNetworkSummary) bool {
335+
return aws.StringValue(snSum.Id) == nameOrId
336+
})
338337

339-
tagsInput := vpclattice.ListTagsForResourceInput{
340-
ResourceArn: r.Arn,
341-
}
338+
switch {
339+
case len(nameMatch) == 0 && len(idMatch) == 0:
340+
return nil, NewNotFoundError("Service network", nameOrId)
341+
case len(nameMatch)+len(idMatch) > 1:
342+
return nil, fmt.Errorf("%w, multiple SN found: nameMatch=%s idMatch=%s",
343+
ErrNameConflict, d.snSummaryToLog(nameMatch), d.snSummaryToLog(idMatch))
344+
case len(nameMatch) == 1:
345+
snMatch = nameMatch[0]
346+
case len(idMatch) == 1:
347+
snMatch = idMatch[0]
348+
default:
349+
return nil, fmt.Errorf("%w: service network match: unreachable", ErrInternal)
350+
}
351+
return snMatch, nil
352+
}
342353

343-
tagsOutput, err2 := d.ListTagsForResourceWithContext(ctx, &tagsInput)
344-
if err2 != nil {
345-
innerErr = err2
346-
return false
347-
}
354+
// checks if given string ARN belongs to given account
355+
func (d *defaultLattice) isLocalResource(strArn string) (bool, error) {
356+
a, err := arn.Parse(strArn)
357+
if err != nil {
358+
return false, err
359+
}
360+
return a.AccountID == d.ownAccount || d.ownAccount == "", nil
361+
}
348362

349-
snMatch = &ServiceNetworkInfo{
350-
SvcNetwork: *r,
351-
Tags: tagsOutput.Tags,
352-
}
353-
return false
354-
}
363+
func (d *defaultLattice) FindServiceNetwork(ctx context.Context, nameOrId string) (*ServiceNetworkInfo, error) {
364+
// When default service network is provided, override for any kind of SN search
365+
if config.ServiceNetworkOverrideMode {
366+
nameOrId = config.DefaultServiceNetwork
367+
}
355368

356-
return true
357-
})
369+
input := &vpclattice.ListServiceNetworksInput{}
370+
allSn, err := d.ListServiceNetworksAsList(ctx, input)
371+
if err != nil {
372+
return nil, err
373+
}
358374

359-
if innerErr != nil {
360-
return nil, innerErr
375+
snMatch, err := d.serviceNetworkMatch(allSn, nameOrId)
376+
if err != nil {
377+
return nil, err
361378
}
379+
380+
// try to fetch tags only if SN in the same aws account with controller's config
381+
tags := Tags{}
382+
isLocal, err := d.isLocalResource(aws.StringValue(snMatch.Arn))
362383
if err != nil {
363384
return nil, err
364385
}
365-
if snMatch == nil {
366-
return nil, NewNotFoundError("Service network", name)
386+
if isLocal {
387+
tagsInput := vpclattice.ListTagsForResourceInput{ResourceArn: snMatch.Arn}
388+
tagsOutput, err := d.ListTagsForResourceWithContext(ctx, &tagsInput)
389+
if err != nil {
390+
aerr, ok := err.(awserr.Error)
391+
// In case ownAccount is not set, we cant tell if SN is foreign.
392+
// In this case access denied is expected.
393+
if !ok || aerr.Code() != vpclattice.ErrCodeAccessDeniedException {
394+
return nil, err
395+
}
396+
}
397+
tags = tagsOutput.Tags
367398
}
368399

369-
return snMatch, nil
400+
return &ServiceNetworkInfo{
401+
SvcNetwork: *snMatch,
402+
Tags: tags,
403+
}, nil
370404
}
371405

372406
// see utils.LatticeServiceName
@@ -394,19 +428,6 @@ func (d *defaultLattice) FindService(ctx context.Context, latticeServiceName str
394428
return svcMatch, nil
395429
}
396430

397-
func accountIdMatches(accountId string, itemArn string) (bool, error) {
398-
if accountId == "" {
399-
return true, nil
400-
}
401-
402-
parsedArn, err := arn.Parse(itemArn)
403-
if err != nil {
404-
return false, err
405-
}
406-
407-
return accountId == parsedArn.AccountID, nil
408-
}
409-
410431
func IsLatticeAPINotFoundErr(err error) bool {
411432
if err == nil {
412433
return false

pkg/aws/services/vpclattice_mocks.go

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)