@@ -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+
2533type 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-
3938func NewNotFoundError (resourceType string , name string ) error {
40- return & NotFoundError { resourceType , name }
39+ return fmt . Errorf ( "%w, %s %s" , ErrNotFound , resourceType , name )
4140}
4241
4342func 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
5351func 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
111109type 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
133135func (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-
410431func IsLatticeAPINotFoundErr (err error ) bool {
411432 if err == nil {
412433 return false
0 commit comments