Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion cmd/adapter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
grpc_proxy_v1 "github.com/sgnl-ai/adapter-framework/pkg/grpc_proxy/v1"
"github.com/sgnl-ai/adapter-framework/server"
aws "github.com/sgnl-ai/adapters/pkg/aws"
awsidentitycenter "github.com/sgnl-ai/adapters/pkg/aws-identitycenter"
aws_s3 "github.com/sgnl-ai/adapters/pkg/aws-s3"
"github.com/sgnl-ai/adapters/pkg/azuread"
"github.com/sgnl-ai/adapters/pkg/bamboohr"
Expand Down Expand Up @@ -85,7 +86,7 @@ func main() {
logger.Fatalf("Failed to create a datasource to query AWS S3: %v", err)
}

// Initialize the client to fetch data from AWS.
// Initialize the client to fetch data from AWS IAM.
awsClient, err := aws.NewClient(
client.NewSGNLHTTPClientWithProxy(timeout, "sgnl-AWS/1.0.0",
grpc_proxy_v1.NewProxyServiceClient(connectorServiceClient),
Expand All @@ -95,8 +96,19 @@ func main() {
logger.Fatalf("Failed to create a datasource to query AWS: %v", err)
}

// Initialize the client to fetch data from AWS Identity Center.
awsICClient, err := awsidentitycenter.NewClient(
client.NewSGNLHTTPClientWithProxy(timeout, "sgnl-AWSIdentityCenter/1.0.0",
grpc_proxy_v1.NewProxyServiceClient(connectorServiceClient),
), nil,
)
if err != nil {
logger.Fatalf("Failed to create a datasource to query AWS: %v", err)
}

// Register adapters here alphabetically.
server.RegisterAdapter(adapterServer, "AWS-1.0.0", aws.NewAdapter(awsClient))
server.RegisterAdapter(adapterServer, "AWSIdentityCenter-1.0.0", awsidentitycenter.NewAdapter(awsICClient))
server.RegisterAdapter(
adapterServer,
"AzureAD-1.0.1",
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.29.14
github.com/aws/aws-sdk-go-v2/credentials v1.17.67
github.com/aws/aws-sdk-go-v2/service/iam v1.41.1
github.com/aws/aws-sdk-go-v2/service/identitystore v1.28.3
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.3
github.com/aws/aws-sdk-go-v2/service/ssoadmin v1.31.0
github.com/aws/smithy-go v1.22.3
github.com/bwmarrin/go-objectsid v0.0.0-20191126144531-5fee401a2f37
github.com/docker/go-connections v0.5.0
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcu
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs=
github.com/aws/aws-sdk-go-v2/service/iam v1.41.1 h1:Kq3R+K49y23CGC5UQF3Vpw5oZEQk5gF/nn+MekPD0ZY=
github.com/aws/aws-sdk-go-v2/service/iam v1.41.1/go.mod h1:mPJkGQzeCoPs82ElNILor2JzZgYENr4UaSKUT8K27+c=
github.com/aws/aws-sdk-go-v2/service/identitystore v1.26.0/go.mod h1:zVLejeKzvUdQD69k8ladCxzC7SnlG1EJwJloK21x/QM=
github.com/aws/aws-sdk-go-v2/service/identitystore v1.28.3 h1:zQMIlYXYHFzrurTKozpXFTGs0M5kwWgG8jL8EKGp8xg=
github.com/aws/aws-sdk-go-v2/service/identitystore v1.28.3/go.mod h1:7nGvrQXBNp7k5yYpwpmxGucYTPY39d0cxjmANAeWwYE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.1 h1:4nm2G6A4pV9rdlWzGMPv4BNtQp22v1hg3yrtkYpeLl8=
Expand All @@ -53,6 +56,8 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.79.3 h1:BRXS0U76Z8wfF+bnkilA2QwpIch6U
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.3/go.mod h1:bNXKFFyaiVvWuR6O16h/I1724+aXe/tAkA9/QS01t5k=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/ssoadmin v1.31.0 h1:mBlGhCX5dS6Z1qUiVMrUFaAZiZZ5ARYTrKLOBGfq0EU=
github.com/aws/aws-sdk-go-v2/service/ssoadmin v1.31.0/go.mod h1:znVkl7Y14sZKEL/sbRQ6qgD8wj8VdTcVVQp5iRaKXcc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY=
Expand Down
97 changes: 97 additions & 0 deletions pkg/aws-identitycenter/adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package awsidentitycenter

Check failure on line 1 in pkg/aws-identitycenter/adapter.go

View workflow job for this annotation

GitHub Actions / lint

Missed header for check (goheader)

import (
"context"
"fmt"
"time"

framework "github.com/sgnl-ai/adapter-framework"
api_adapter_v1 "github.com/sgnl-ai/adapter-framework/api/adapter/v1"
"github.com/sgnl-ai/adapter-framework/web"
"github.com/sgnl-ai/adapters/pkg/config"
"github.com/sgnl-ai/adapters/pkg/pagination"
)

// Adapter implements the framework.Adapter interface to query pages of objects
// from datasources.
type Adapter struct {
Client Client
}

// NewAdapter instantiates a new Adapter.
func NewAdapter(client Client) framework.Adapter[Config] {
return &Adapter{Client: client}
}

// GetPage is called by SGNL's ingestion service to query a page of objects
// from a datasource.
func (a *Adapter) GetPage(ctx context.Context, request *framework.Request[Config]) framework.Response {
if err := a.ValidateGetPageRequest(ctx, request); err != nil {
return framework.NewGetPageResponseError(err)
}

return a.RequestPageFromDatasource(ctx, request)
}

// RequestPageFromDatasource requests a page of objects from a datasource.
func (a *Adapter) RequestPageFromDatasource(
ctx context.Context, request *framework.Request[Config],
) framework.Response {
var commonConfig *config.CommonConfig
if request.Config != nil {
commonConfig = request.Config.CommonConfig
}

commonConfig = config.SetMissingCommonConfigDefaults(commonConfig)

// Unmarshal the current cursor.
cursor, err := pagination.UnmarshalCursor[string](request.Cursor)
if err != nil {
return framework.NewGetPageResponseError(err)
}

awsReq := &Request{
Auth: Auth{
AccessKey: request.Auth.Basic.Username,
SecretKey: request.Auth.Basic.Password,
Region: request.Config.Region,
},
IdentityStoreID: request.Config.IdentityStoreID,
InstanceARN: request.Config.InstanceARN,
MaxResults: int32(request.PageSize),
EntityExternalID: request.Entity.ExternalId,
Cursor: cursor,
RequestTimeoutSeconds: *commonConfig.RequestTimeoutSeconds,
}

resp, err := a.Client.GetPage(ctx, awsReq)
if err != nil {
return framework.NewGetPageResponseError(err)
}

if adapterErr := web.HTTPError(resp.StatusCode, resp.RetryAfterHeader); adapterErr != nil {
return framework.NewGetPageResponseError(adapterErr)
}

parsedObjects, parserErr := web.ConvertJSONObjectList(
&request.Entity,
resp.Objects,
web.WithJSONPathAttributeNames(),
web.WithLocalTimeZoneOffset(commonConfig.LocalTimeZoneOffset),
web.WithDateTimeFormats(
[]web.DateTimeFormatWithTimeZone{{Format: time.RFC3339, HasTimeZone: true}}...,
),
)
if parserErr != nil {
return framework.NewGetPageResponseError(
&framework.Error{Message: fmt.Sprintf("Failed to convert datasource response objects: %v.", parserErr), Code: api_adapter_v1.ErrorCode_ERROR_CODE_INTERNAL},

Check failure on line 87 in pkg/aws-identitycenter/adapter.go

View workflow job for this annotation

GitHub Actions / lint

The line is 159 characters long, which exceeds the maximum of 120 characters. (lll)
)
}

nextCursor, err := pagination.MarshalCursor(resp.NextCursor)
if err != nil {
return framework.NewGetPageResponseError(err)
}

return framework.NewGetPageResponseSuccess(&framework.Page{Objects: parsedObjects, NextCursor: nextCursor})
}
62 changes: 62 additions & 0 deletions pkg/aws-identitycenter/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package awsidentitycenter

Check failure on line 1 in pkg/aws-identitycenter/client.go

View workflow job for this annotation

GitHub Actions / lint

Missed header for check (goheader)

import (
"context"

framework "github.com/sgnl-ai/adapter-framework"
"github.com/sgnl-ai/adapters/pkg/pagination"
)

// Client is a client that allows querying the datasource which contains JSON objects.
type Client interface {
GetPage(ctx context.Context, request *Request) (*Response, *framework.Error)
}

type Auth struct {
// AccessKey is the access key to authenticate with AWS.
AccessKey string

// SecretKey is the secret key to authenticate with AWS.
SecretKey string

// Region is the AWS region to query.
Region string
}

// Request is a request to the datasource.
type Request struct {
Auth

// IdentityStoreID is the AWS Identity Store identifier.
IdentityStoreID string

// InstanceARN is the AWS Identity Center instance ARN.
InstanceARN string

// MaxResults is the maximum number of objects to return from the entity.
MaxResults int32

// EntityExternalID is the external ID of the entity.
EntityExternalID string

// Cursor identifies the first object of the page to return.
Cursor *pagination.CompositeCursor[string]

// RequestTimeoutSeconds is the timeout duration for requests made to datasources.
RequestTimeoutSeconds int
}

// Response is a response returned by the datasource.
type Response struct {
// StatusCode is an HTTP status code.
StatusCode int

// RetryAfterHeader is the Retry-After response HTTP header, if set.
RetryAfterHeader string

// Objects is the list of items returned by the datasource.
Objects []map[string]any

// NextCursor is the cursor that identifies the first object of the next page.
NextCursor *pagination.CompositeCursor[string]
}
21 changes: 21 additions & 0 deletions pkg/aws-identitycenter/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package awsidentitycenter_test

import (
framework "github.com/sgnl-ai/adapter-framework"
adapter "github.com/sgnl-ai/adapters/pkg/aws-identitycenter"
)

var (
validAuthCredentials = &framework.DatasourceAuthCredentials{
Basic: &framework.BasicAuthCredentials{
Username: "access",
Password: "secret",
},
}

validConfig = &adapter.Config{
Region: "us-west-2",
IdentityStoreID: "d-1234567890",
InstanceARN: "arn:aws:sso:::instance/ssoins-1234567890",
}
)
46 changes: 46 additions & 0 deletions pkg/aws-identitycenter/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package awsidentitycenter

Check failure on line 1 in pkg/aws-identitycenter/config.go

View workflow job for this annotation

GitHub Actions / lint

Missed header for check (goheader)

import (
"context"
"errors"

"github.com/sgnl-ai/adapters/pkg/config"
)

// Config is the configuration passed in each GetPage calls to the adapter.
// AWS Identity Center Adapter configuration example:
//
// {
// "region": "us-west-2",
// "identityStoreID": "d-1234567890",
// "instanceARN": "arn:aws:sso:::instance/ssoins-1234567890"
// }

type Config struct {
*config.CommonConfig

// Region is the AWS region to query.
Region string `json:"region"`

// IdentityStoreID is the AWS Identity Store identifier.
IdentityStoreID string `json:"identityStoreID"`

// InstanceARN is the AWS Identity Center instance ARN.
InstanceARN string `json:"instanceARN"`
}

// ValidateConfig validates that a Config received in a GetPage call is valid.
func (c *Config) Validate(_ context.Context) error {
switch {
case c == nil:
return errors.New("the request contains an empty configuration")
case c.Region == "":
return errors.New("the AWS Region is not set in the configuration")
case c.IdentityStoreID == "":
return errors.New("identityStoreID is not set in the configuration")
case c.InstanceARN == "":
return errors.New("instanceARN is not set in the configuration")
default:
return nil
}
}
Loading
Loading