Skip to content

Commit a75d718

Browse files
authored
Merge pull request #107 from scribd/maksimt/SERF-3389/aws-config
[SERF-3389] Add AWS service support
2 parents bc49b52 + 644461f commit a75d718

File tree

9 files changed

+842
-12
lines changed

9 files changed

+842
-12
lines changed

README.md

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ SDK, the Go version.
3232
- [Kafka specific configuration](#kafka-specific-configuration)
3333
- [Cache](#cache)
3434
- [Redis](#redis-specific-configuration)
35+
- [AWS configuration](#aws-configuration)
36+
- [AWS Common configuration](#aws-common-configuration)
37+
- [AWS Service configuration](#aws-service-configuration)
3538
- [APM & Instrumentation](#apm---instrumentation)
3639
- [Request ID middleware](#request-id-middleware)
3740
- [HTTP server Request ID middleware](#http-server-request-id-middleware)
@@ -97,11 +100,12 @@ The list of predefined top-level configurations:
97100
`config/logger.yml` configuration file.
98101
* `Database`, containing the application database configuration, expects a
99102
`config/database.yml` configuration file.
100-
* `Redis`, containing the application Redis configuration, expects a
101-
`config/redis.yml` configuration file. (TBD)
103+
* `Cache`, containing the application Cache configuration, expects a
104+
`config/cache.yml` configuration file.
102105
* `PubSub`, containing the application pubsub configuration, expects a
103106
`config/pubsub.yml` configuration file.
104-
* <insert new configuration here>
107+
* `AWS`, containing the application AWS configuration, expects a
108+
`config/aws.yml` configuration file.
105109

106110
For example, to get the host and the port at which the HTTP server listens on
107111
in an application:
@@ -983,6 +987,80 @@ To secure the requests to Redis, Go SDK provides a configuration set for TLS:
983987
| Passphrase | Passphrase is used in case the private key needs to be decrypted | `passphrase` | `APP_CACHE_REDIS_TLS_PASSPHRASE` | string | pass phrase |
984988
| Skip TLS verification | Turn on / off TLS verification | `insecure_skip_verify` | `APP_CACHE_REDIS_TLS_INSECURE_SKIP_VERIFY` | bool | true, false |
985989

990+
991+
### AWS configuration
992+
993+
`go-sdk` provides a convenient way to create an AWS configuration.
994+
995+
Configuration contains the common configuration for AWS configuration and specific configuration for services.
996+
997+
Service configuration is split by the service name to facilitate the configuration of multiple services per service type.
998+
999+
```yaml
1000+
common: &common
1001+
config:
1002+
region: "us-east-2"
1003+
s3:
1004+
default:
1005+
# APP_AWS_S3_DEFAULT_REGION
1006+
region: "us-east-1"
1007+
credentials:
1008+
assume_role:
1009+
# APP_AWS_S3_DEFAULT_CREDENTIALS_ASSUME_ROLE_ARN
1010+
arn: ""
1011+
example:
1012+
# APP_AWS_S3_EXAMPLE_REGION
1013+
region: "us-west-2"
1014+
credentials:
1015+
static:
1016+
# APP_AWS_S3_EXAMPLE_CREDENTIALS_STATIC_ACCESS_KEY_ID
1017+
access_key_id: ""
1018+
# APP_AWS_S3_EXAMPLE_CREDENTIALS_STATIC_SECRET_ACCESS_KEY
1019+
secret_access_key: ""
1020+
# APP_AWS_S3_EXAMPLE_CREDENTIALS_STATIC_SESSION_TOKEN
1021+
session_token: ""
1022+
```
1023+
1024+
#### AWS common configuration
1025+
1026+
Common configuration contains the following options:
1027+
1028+
| Setting | Description | YAML variable | Environment variable (ENV) | Type | Possible Values |
1029+
|----------------------------|--------------------------------------------------------|------------------|--------------------------------------|--------|-----------------|
1030+
| Region | AWS region to use | `region` | `APP_AWS_REGION` | string | us-west-2 |
1031+
| HTTP client max idle conns | Maximum number of idle connections in the HTTP client. | `max_idle_conns` | `APP_AWS_HTTP_CLIENT_MAX_IDLE_CONNS` | int | 100 |
1032+
1033+
#### AWS Service configuration
1034+
1035+
Currently, `go-sdk` supports the following AWS service types: `s3`, `sagemakerruntime`, `sfn` and `sqs`.
1036+
1037+
To facilitate the configuration of multiple services per service type, the configuration is split by the service name.
1038+
To override the settings specified in the YAML via the environment variables, the following naming convention is used:
1039+
1040+
```
1041+
APP_AWS_<SERVICE_TYPE>_<SERVICE_NAME>_*
1042+
```
1043+
1044+
Where `SERVICE_TYPE` is the service type (s3, sqs etc.) and `SERVICE_NAME` is the service name (default, example etc.).
1045+
1046+
It is possible to specify region and HTTP client settings for each service, which will override the common configuration.
1047+
1048+
For each service, it is possible to specify credentials. Currently, `go-sdk` supports two types of credentials: `assume_role` and `static`.
1049+
1050+
`assume_role` credentials are used to assume a role to get credentials using AWS STS service. The following options are available:
1051+
1052+
| Setting | Description | YAML variable | Environment variable (ENV) | Type | Possible Values |
1053+
|---------|------------------------------------------------------|---------------|--------------------------------------------------|--------|------------------------------------------|
1054+
| ARN | The Amazon Resource Name (ARN) of the role to assume | `arn` | `APP_AWS_S3_DEFAULT_CREDENTIALS_ASSUME_ROLE_ARN` | string | arn:aws:iam::123456789012:role/role-name |
1055+
1056+
`static` credentials are used to specify the access key ID, secret access key and session token. The following options are available:
1057+
1058+
| Setting | Description | YAML variable | Environment variable (ENV) | Type | Possible Values |
1059+
|-------------------|-----------------------|---------------------|-----------------------------------------------------------|--------|-------------------|
1060+
| Access Key ID | The access key ID | `access_key_id` | `APP_AWS_S3_EXAMPLE_CREDENTIALS_STATIC_ACCESS_KEY_ID` | string | access-key-id |
1061+
| Secret Access Key | The secret access key | `secret_access_key` | `APP_AWS_S3_EXAMPLE_CREDENTIALS_STATIC_SECRET_ACCESS_KEY` | string | secret-access-key |
1062+
| Session Token | The session token | `session_token` | `APP_AWS_S3_EXAMPLE_CREDENTIALS_STATIC_SESSION_TOKEN` | string | session-token |
1063+
9861064
## APM & Instrumentation
9871065

9881066
The `go-sdk` provides an easy way to add application performance monitoring
@@ -1192,25 +1270,29 @@ import (
11921270
"context"
11931271
"log"
11941272
1195-
"github.com/aws/aws-sdk-go-v2/aws"
1196-
awscfg "github.com/aws/aws-sdk-go-v2/config"
1197-
"github.com/aws/aws-sdk-go-v2/service/s3"
1198-
1273+
sdkaws "github.com/scribd/go-sdk/pkg/aws"
1274+
sdkconfig "github.com/scribd/go-sdk/pkg/configuration"
11991275
instrumentation "github.com/scribd/go-sdk/pkg/instrumentation"
12001276
)
12011277
12021278
func main() {
1203-
cfg, err := awscfg.LoadDefaultConfig(context.Background, awscfg.WithRegion("us-west-2"))
1279+
config, err := sdkconfig.NewConfig()
12041280
if err != nil {
1205-
log.Fatalf("error: %v", err)
1281+
log.Fatalf("Failed to load SDK config: %s", err)
12061282
}
12071283
1284+
awsBuilder := sdkaws.NewBuilder(config.AWS)
1285+
1286+
cfg, err := awsBuilder.LoadConfig(context.Background())
1287+
if err != nil {
1288+
log.Fatalf("error: %v", err)
1289+
}
12081290
instrumentation.InstrumentAWSClient(cfg, instrumentation.Settings{
12091291
AppName: applicationName,
12101292
})
12111293
1212-
// Use the AWS configuration to create clients, such as AWS S3...
1213-
s3client := s3.NewFromConfig(cfg)
1294+
// Create an S3 service client with the service name "default"
1295+
s3client := awsBuilder.NewS3Service(cfg, "default")
12141296
}
12151297
```
12161298

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ require (
99
github.com/aws/aws-sdk-go v1.44.327
1010
github.com/aws/aws-sdk-go-v2 v1.26.1
1111
github.com/aws/aws-sdk-go-v2/config v1.27.11
12+
github.com/aws/aws-sdk-go-v2/credentials v1.17.11
1213
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1
14+
github.com/aws/aws-sdk-go-v2/service/sagemakerruntime v1.27.4
1315
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6
1416
github.com/getsentry/sentry-go v0.12.0
1517
github.com/go-kit/kit v0.9.0
@@ -43,7 +45,6 @@ require (
4345
github.com/DataDog/sketches-go v1.4.2 // indirect
4446
github.com/Microsoft/go-winio v0.6.1 // indirect
4547
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect
46-
github.com/aws/aws-sdk-go-v2/credentials v1.17.11 // indirect
4748
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect
4849
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
4950
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ github.com/aws/aws-sdk-go-v2/service/kinesis v1.18.4 h1:UohaQds+Puk9BEbvncXkZduI
7777
github.com/aws/aws-sdk-go-v2/service/kinesis v1.18.4/go.mod h1:HnjgmL8TNmYtGcrA3N6EeCnDvlX6CteCdUbZ1wV8QWQ=
7878
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1 h1:6cnno47Me9bRykw9AEv9zkXE+5or7jz8TsskTTccbgc=
7979
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1/go.mod h1:qmdkIIAC+GCLASF7R2whgNrJADz0QZPX+Seiw/i4S3o=
80+
github.com/aws/aws-sdk-go-v2/service/sagemakerruntime v1.27.4 h1:hNp4PzD2N9qTqJAlrP0GAwDTKc2FTNLh6DVFzurLMrE=
81+
github.com/aws/aws-sdk-go-v2/service/sagemakerruntime v1.27.4/go.mod h1:oPtVhWs6TuHOxUPQpNDtaQoVGjO5DbEfUWfzOxqZDOE=
8082
github.com/aws/aws-sdk-go-v2/service/sfn v1.19.4 h1:yIyFY2kbCOoHvuivf9minqnP2RLYJgmvQRYxakIb2oI=
8183
github.com/aws/aws-sdk-go-v2/service/sfn v1.19.4/go.mod h1:uWCH4ATwNrkRO40j8Dmy7u/Y1/BVWgCM+YjBNYZeOro=
8284
github.com/aws/aws-sdk-go-v2/service/sns v1.21.4 h1:Asj098jPfIZYzAbk4xVFwVBGij5hgMcli0d+5Pe4aZA=

pkg/aws/aws.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package aws
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/aws/aws-sdk-go-v2/aws"
9+
awscfg "github.com/aws/aws-sdk-go-v2/config"
10+
"github.com/aws/aws-sdk-go-v2/credentials"
11+
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
12+
"github.com/aws/aws-sdk-go-v2/service/s3"
13+
"github.com/aws/aws-sdk-go-v2/service/sagemakerruntime"
14+
"github.com/aws/aws-sdk-go-v2/service/sts"
15+
)
16+
17+
type (
18+
Builder struct {
19+
config *Config
20+
}
21+
)
22+
23+
func NewBuilder(c *Config) *Builder {
24+
return &Builder{config: c}
25+
}
26+
27+
func (b *Builder) LoadConfig(
28+
ctx context.Context, opts ...func(options *awscfg.LoadOptions) error) (aws.Config, error) {
29+
defaultOpts := []func(options *awscfg.LoadOptions) error{
30+
awscfg.WithRegion(b.config.AWSConfig.Region),
31+
awscfg.WithHTTPClient(createHttpClient(&b.config.AWSConfig.HTTPClient)),
32+
}
33+
34+
opts = append(defaultOpts, opts...)
35+
36+
return awscfg.LoadDefaultConfig(ctx, opts...)
37+
}
38+
39+
func (b *Builder) NewS3Service(
40+
cfg aws.Config, serviceName string, opts ...func(options *s3.Options)) (*s3.Client, error) {
41+
s3Cfg, ok := b.config.S3[serviceName]
42+
if !ok {
43+
return nil, fmt.Errorf("s3 config for service %s not found", serviceName)
44+
}
45+
defaultOpts := []func(*s3.Options){
46+
func(options *s3.Options) {
47+
if s3Cfg.Region != "" {
48+
options.Region = s3Cfg.Region
49+
}
50+
if credentialsSet(&s3Cfg.Credentials) {
51+
options.Credentials = getCredentialsProvider(cfg, &s3Cfg.Credentials)
52+
}
53+
if s3Cfg.HTTPClient.MaxIdleConns > 0 {
54+
options.HTTPClient = createHttpClient(&s3Cfg.HTTPClient)
55+
}
56+
},
57+
}
58+
59+
opts = append(defaultOpts, opts...)
60+
61+
return s3.NewFromConfig(cfg, opts...), nil
62+
}
63+
64+
func (b *Builder) NewSagemakerRuntimeService(
65+
cfg aws.Config,
66+
serviceName string, opts ...func(options *sagemakerruntime.Options)) (*sagemakerruntime.Client, error) {
67+
sagemakerRuntimeCfg, ok := b.config.SagemakerRuntime[serviceName]
68+
if !ok {
69+
return nil, fmt.Errorf("sagemaker runtime config for service %s not found", serviceName)
70+
}
71+
defaultOpts := []func(*sagemakerruntime.Options){
72+
func(options *sagemakerruntime.Options) {
73+
if sagemakerRuntimeCfg.Region != "" {
74+
options.Region = sagemakerRuntimeCfg.Region
75+
}
76+
if credentialsSet(&sagemakerRuntimeCfg.Credentials) {
77+
options.Credentials = getCredentialsProvider(cfg, &sagemakerRuntimeCfg.Credentials)
78+
}
79+
if sagemakerRuntimeCfg.HTTPClient.MaxIdleConns > 0 {
80+
options.HTTPClient = createHttpClient(&sagemakerRuntimeCfg.HTTPClient)
81+
}
82+
},
83+
}
84+
85+
opts = append(defaultOpts, opts...)
86+
87+
return sagemakerruntime.NewFromConfig(cfg, opts...), nil
88+
}
89+
90+
func (b *Builder) NewSFNService(
91+
cfg aws.Config,
92+
serviceName string, opts ...func(options *sagemakerruntime.Options)) (*sagemakerruntime.Client, error) {
93+
sfnCfg, ok := b.config.SFN[serviceName]
94+
if !ok {
95+
return nil, fmt.Errorf("sfn config for service %s not found", serviceName)
96+
}
97+
defaultOpts := []func(*sagemakerruntime.Options){
98+
func(options *sagemakerruntime.Options) {
99+
if sfnCfg.Region != "" {
100+
options.Region = sfnCfg.Region
101+
}
102+
if credentialsSet(&sfnCfg.Credentials) {
103+
options.Credentials = getCredentialsProvider(cfg, &sfnCfg.Credentials)
104+
}
105+
if sfnCfg.HTTPClient.MaxIdleConns > 0 {
106+
options.HTTPClient = createHttpClient(&sfnCfg.HTTPClient)
107+
}
108+
},
109+
}
110+
111+
opts = append(defaultOpts, opts...)
112+
113+
return sagemakerruntime.NewFromConfig(cfg, opts...), nil
114+
}
115+
116+
func (b *Builder) NewSQSService(
117+
cfg aws.Config, serviceName string, opts ...func(options *s3.Options)) (*s3.Client, error) {
118+
sqsCfg, ok := b.config.SQS[serviceName]
119+
if !ok {
120+
return nil, fmt.Errorf("sqs config for service %s not found", serviceName)
121+
}
122+
defaultOpts := []func(*s3.Options){
123+
func(options *s3.Options) {
124+
if sqsCfg.Region != "" {
125+
options.Region = sqsCfg.Region
126+
}
127+
if credentialsSet(&sqsCfg.Credentials) {
128+
options.Credentials = getCredentialsProvider(cfg, &sqsCfg.Credentials)
129+
}
130+
if sqsCfg.HTTPClient.MaxIdleConns > 0 {
131+
options.HTTPClient = createHttpClient(&sqsCfg.HTTPClient)
132+
}
133+
},
134+
}
135+
136+
opts = append(defaultOpts, opts...)
137+
138+
return s3.NewFromConfig(cfg, opts...), nil
139+
}
140+
141+
func createHttpClient(cfg *HTTPClient) *http.Client {
142+
defaultRoundTripper := http.DefaultTransport
143+
defaultTransport := defaultRoundTripper.(*http.Transport)
144+
145+
httpTransport := defaultTransport.Clone()
146+
httpTransport.MaxIdleConnsPerHost = cfg.MaxIdleConns
147+
148+
return &http.Client{Transport: httpTransport}
149+
}
150+
151+
func credentialsSet(cfg *CredentialsConfig) bool {
152+
return cfg.Static.AccessKeyID != "" &&
153+
cfg.Static.SecretAccessKey != "" ||
154+
cfg.AssumeRole.ARN != ""
155+
}
156+
157+
func getCredentialsProvider(
158+
awsConf aws.Config, cfg *CredentialsConfig) aws.CredentialsProvider {
159+
if cfg.AssumeRole.ARN != "" {
160+
return aws.NewCredentialsCache(
161+
stscreds.NewAssumeRoleProvider(
162+
sts.NewFromConfig(awsConf),
163+
cfg.AssumeRole.ARN))
164+
}
165+
return credentials.NewStaticCredentialsProvider(
166+
cfg.Static.AccessKeyID,
167+
cfg.Static.SecretAccessKey,
168+
cfg.Static.SessionToken)
169+
}

0 commit comments

Comments
 (0)