// Copyright IBM Corp. 2021, 2026 // SPDX-License-Identifier: MPL-2.0 package host import ( "fmt" cred "github.com/hashicorp/boundary-plugin-aws/internal/credential" "github.com/hashicorp/boundary-plugin-aws/internal/errors" "github.com/hashicorp/boundary-plugin-aws/internal/values" "github.com/mitchellh/mapstructure" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/structpb" ) // CatalogAttributes is a Go-native representation of the Attributes // map. type CatalogAttributes struct { *cred.CredentialAttributes // DualStack is used for configuring how the aws client will resolve requests. DualStack bool // InstanceAddressesOnly specifies whether only the instance addresses (private IPv4/DNS, // public IPv4/DNS, and IPv6) are synced, or all addresses, including those from // secondary ENIs, are synced. InstanceAddressesOnly bool } func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) { unknownFields := values.StructFields(in) badFields := make(map[string]string) var err error credAttributes, err := cred.GetCredentialAttributes(in) if err != nil { return nil, err } dualStack, err := values.GetBoolValue(in, ConstAwsDualStack, false) if err != nil { badFields[fmt.Sprintf("attributes.%s", ConstAwsDualStack)] = err.Error() } delete(unknownFields, ConstAwsDualStack) instanceAddressesOnly, err := values.GetBoolValue(in, ConstInstanceAddressesOnly, false) if err != nil { badFields[fmt.Sprintf("attributes.%s", ConstInstanceAddressesOnly)] = err.Error() } delete(unknownFields, ConstInstanceAddressesOnly) for s := range unknownFields { switch s { // Ignore knownFields from CredentialAttributes case cred.ConstRegion: continue case cred.ConstDisableCredentialRotation: continue case cred.ConstRoleArn: continue case cred.ConstRoleExternalId: continue case cred.ConstRoleSessionName: continue case cred.ConstRoleTags: continue default: badFields[fmt.Sprintf("attributes.%s", s)] = "unrecognized field" } } if len(badFields) > 0 { return nil, errors.InvalidArgumentError("Invalid arguments in catalog attributes", badFields) } return &CatalogAttributes{ CredentialAttributes: credAttributes, DualStack: dualStack, InstanceAddressesOnly: instanceAddressesOnly, }, nil } // SetAttributes is a Go-native representation of the Attributes map that can be // used for decoding the incoming map via mapstructure. type SetAttributes struct { Filters []string } func getSetAttributes(in *structpb.Struct) (*SetAttributes, error) { var setAttrs SetAttributes // Quick validation to ensure that there's no non-filter attributes // here for now. Make this more complex if we add more attributes // to host sets. unknownFields := values.StructFields(in) badFields := make(map[string]string) delete(unknownFields, ConstDescribeInstancesFilters) for a := range unknownFields { badFields[fmt.Sprintf("attributes.%s", a)] = "unrecognized field" } if len(badFields) > 0 { return nil, errors.InvalidArgumentError("Error in the attributes provided", badFields) } // Mapstructure complains if it expects a slice as output and sees a scalar // value. Rather than use WeakDecode and risk unintended consequences, I'm // manually making this change if necessary. inMap := in.AsMap() if filtersRaw, ok := inMap[ConstDescribeInstancesFilters]; ok { switch filterVal := filtersRaw.(type) { case string: inMap[ConstDescribeInstancesFilters] = []string{filterVal} } } if err := mapstructure.Decode(inMap, &setAttrs); err != nil { return nil, status.Errorf(codes.InvalidArgument, "error decoding set attributes: %s", err) } return &setAttrs, nil }