Skip to content

Commit dc04cde

Browse files
authored
Fix cache architecture (#82)
* fix cache architecture * update Docs
1 parent 2e401b4 commit dc04cde

31 files changed

+1084
-567
lines changed

README.md

+20
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,26 @@ See [Godoc](https://pkg.go.dev/github.com/dwango/yashiro).
1717
go get github.com/dwango/yashiro
1818
```
1919

20+
### Authorization
21+
22+
AWS
23+
24+
```json
25+
{
26+
"Version": "2012-10-17",
27+
"Statement": [
28+
{
29+
"Effect": "Allow",
30+
"Action": [
31+
"ssm:GetParameter",
32+
"secretsmanager:GetSecretValue"
33+
],
34+
"Resource": ["*"],
35+
},
36+
]
37+
}
38+
```
39+
2040
## CLI Tool
2141

2242
### Installation

alias.go

+3
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,7 @@ type Config = config.Config
3030
var (
3131
// NewEngine returns a new Engine.
3232
NewEngine = engine.New
33+
34+
// IgnoreNotFound is an option to ignore missing external store values.
35+
IgnoreNotFound = engine.IgnoreNotFound
3336
)

example_test.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"log"
2222
"os"
23+
"time"
2324

2425
awsconfig "github.com/aws/aws-sdk-go-v2/config"
2526
"github.com/dwango/yashiro"
@@ -36,6 +37,13 @@ func Example() {
3637

3738
refName := "example"
3839
cfg := &config.Config{
40+
Global: config.GlobalConfig{
41+
EnableCache: true, // enable cache
42+
Cache: config.CacheConfig{
43+
Type: config.CacheTypeMemory,
44+
ExpireDuration: config.Duration(30 * time.Minute),
45+
},
46+
},
3947
Aws: &config.AwsConfig{
4048
ParameterStoreValues: []config.AwsParameterStoreValueConfig{
4149
{
@@ -50,7 +58,7 @@ func Example() {
5058
},
5159
}
5260

53-
eng, err := yashiro.NewEngine(cfg)
61+
eng, err := yashiro.NewEngine(cfg, yashiro.IgnoreNotFound(true)) // ignore not found value
5462
if err != nil {
5563
log.Fatalf("failed to create engine: %s", err)
5664
}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/aws/aws-sdk-go-v2/config v1.27.16
99
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.29.1
1010
github.com/aws/aws-sdk-go-v2/service/ssm v1.50.4
11+
github.com/aws/aws-sdk-go-v2/service/sts v1.28.10
1112
github.com/spf13/cobra v1.8.0
1213
golang.org/x/crypto v0.3.0
1314
sigs.k8s.io/yaml v1.4.0
@@ -25,7 +26,6 @@ require (
2526
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 // indirect
2627
github.com/aws/aws-sdk-go-v2/service/sso v1.20.9 // indirect
2728
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3 // indirect
28-
github.com/aws/aws-sdk-go-v2/service/sts v1.28.10 // indirect
2929
github.com/aws/smithy-go v1.20.2 // indirect
3030
github.com/google/uuid v1.1.1 // indirect
3131
github.com/huandu/xstrings v1.3.3 // indirect

internal/client/aws.go

+139-19
Original file line numberDiff line numberDiff line change
@@ -21,39 +21,53 @@ import (
2121
"errors"
2222
"fmt"
2323

24-
kms "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
25-
kmsTypes "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types"
24+
"github.com/aws/aws-sdk-go-v2/aws"
25+
secs "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
26+
secsTypes "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types"
2627
"github.com/aws/aws-sdk-go-v2/service/ssm"
2728
ssmTypes "github.com/aws/aws-sdk-go-v2/service/ssm/types"
29+
"github.com/aws/aws-sdk-go-v2/service/sts"
30+
"github.com/dwango/yashiro/internal/client/cache"
2831
"github.com/dwango/yashiro/internal/values"
2932
"github.com/dwango/yashiro/pkg/config"
3033
)
3134

32-
type ssmClient interface {
33-
GetParameter(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error)
34-
}
35-
36-
type kmsClient interface {
37-
GetSecretValue(ctx context.Context, params *kms.GetSecretValueInput, optFns ...func(*kms.Options)) (*kms.GetSecretValueOutput, error)
38-
}
39-
4035
type awsClient struct {
4136
ssmClient ssmClient
42-
kmsClient kmsClient
37+
secsClient secsClient
4338
parameterStoreValue []config.AwsParameterStoreValueConfig
4439
secretsManagerValue []config.ValueConfig
4540
}
4641

47-
func newAwsClient(cfg *config.AwsConfig) (Client, error) {
48-
if cfg.SdkConfig == nil {
42+
func newAwsClient(cfg *config.Config) (Client, error) {
43+
if cfg.Aws.SdkConfig == nil {
4944
return nil, fmt.Errorf("require aws sdk config")
5045
}
5146

47+
var cc cache.Cache
48+
if cfg.Global.EnableCache {
49+
// get AWS account ID
50+
accountID, err := getAwsAccountId(cfg.Aws.SdkConfig)
51+
if err != nil {
52+
return nil, err
53+
}
54+
cc, err = cache.New(cfg.Global.Cache, cache.WithCacheKeys("aws", cfg.Aws.SdkConfig.Region, accountID))
55+
if err != nil {
56+
return nil, err
57+
}
58+
}
59+
5260
return &awsClient{
53-
ssmClient: ssm.NewFromConfig(*cfg.SdkConfig),
54-
kmsClient: kms.NewFromConfig(*cfg.SdkConfig),
55-
parameterStoreValue: cfg.ParameterStoreValues,
56-
secretsManagerValue: cfg.SecretsManagerValues,
61+
ssmClient: &ssmClientWithCache{
62+
client: ssm.NewFromConfig(*cfg.Aws.SdkConfig),
63+
cache: cc,
64+
},
65+
secsClient: &secsClientWithCache{
66+
client: secs.NewFromConfig(*cfg.Aws.SdkConfig),
67+
cache: cc,
68+
},
69+
parameterStoreValue: cfg.Aws.ParameterStoreValues,
70+
secretsManagerValue: cfg.Aws.SecretsManagerValues,
5771
}, nil
5872
}
5973

@@ -80,12 +94,12 @@ func (c awsClient) GetValues(ctx context.Context, ignoreNotFound bool) (values.V
8094
}
8195

8296
for _, v := range c.secretsManagerValue {
83-
output, err := c.kmsClient.GetSecretValue(ctx, &kms.GetSecretValueInput{
97+
output, err := c.secsClient.GetSecretValue(ctx, &secs.GetSecretValueInput{
8498
SecretId: &v.Name,
8599
})
86100

87101
if err != nil {
88-
var notFoundErr *kmsTypes.ResourceNotFoundException
102+
var notFoundErr *secsTypes.ResourceNotFoundException
89103
if ignoreNotFound && errors.As(err, &notFoundErr) {
90104
continue
91105
}
@@ -99,3 +113,109 @@ func (c awsClient) GetValues(ctx context.Context, ignoreNotFound bool) (values.V
99113

100114
return values, nil
101115
}
116+
117+
type ssmClient interface {
118+
GetParameter(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error)
119+
}
120+
121+
type ssmClientWithCache struct {
122+
client ssmClient
123+
cache cache.Cache
124+
}
125+
126+
func (c ssmClientWithCache) GetParameter(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) {
127+
if c.cache == nil {
128+
return c.getParameter(ctx, params, optFns...)
129+
}
130+
131+
key := *params.Name // Name is required, so do not check nil
132+
isSensitive := params.WithDecryption != nil && *params.WithDecryption
133+
134+
// Load from cache.
135+
value, expired, err := c.cache.Load(ctx, key, isSensitive)
136+
if err != nil {
137+
return nil, err
138+
}
139+
140+
// If a cache value is expired or not found, get a value from the external store.
141+
if value == nil || expired {
142+
output, err := c.getParameter(ctx, params, optFns...)
143+
if err != nil {
144+
return nil, err
145+
}
146+
147+
// Create or update cache.
148+
if err := c.cache.Save(ctx, key, output.Parameter.Value, isSensitive); err != nil {
149+
return nil, err
150+
}
151+
152+
return output, nil
153+
}
154+
155+
return &ssm.GetParameterOutput{Parameter: &ssmTypes.Parameter{Value: value}}, nil
156+
}
157+
158+
func (c ssmClientWithCache) getParameter(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) {
159+
output, err := c.client.GetParameter(ctx, params, optFns...)
160+
if err != nil {
161+
return nil, err
162+
}
163+
return output, nil
164+
}
165+
166+
type secsClient interface {
167+
GetSecretValue(ctx context.Context, params *secs.GetSecretValueInput, optFns ...func(*secs.Options)) (*secs.GetSecretValueOutput, error)
168+
}
169+
170+
type secsClientWithCache struct {
171+
client secsClient
172+
cache cache.Cache
173+
}
174+
175+
func (c secsClientWithCache) GetSecretValue(ctx context.Context, params *secs.GetSecretValueInput, optFns ...func(*secs.Options)) (*secs.GetSecretValueOutput, error) {
176+
if c.cache == nil {
177+
return c.getSecretValue(ctx, params, optFns...)
178+
}
179+
180+
key := *params.SecretId // SecretId is required, so do not check nil
181+
182+
// Load from cache. Secret is always sensitive.
183+
value, expired, err := c.cache.Load(ctx, key, true)
184+
if err != nil {
185+
return nil, err
186+
}
187+
188+
// If a cache value is expired or not found, get a value from the external store.
189+
if value == nil || expired {
190+
output, err := c.getSecretValue(ctx, params, optFns...)
191+
if err != nil {
192+
return nil, err
193+
}
194+
195+
// Create or update cache.
196+
if err := c.cache.Save(ctx, key, output.SecretString, true); err != nil {
197+
return nil, err
198+
}
199+
200+
return output, nil
201+
}
202+
203+
return &secs.GetSecretValueOutput{SecretString: value}, nil
204+
}
205+
206+
func (c secsClientWithCache) getSecretValue(ctx context.Context, params *secs.GetSecretValueInput, optFns ...func(*secs.Options)) (*secs.GetSecretValueOutput, error) {
207+
output, err := c.client.GetSecretValue(ctx, params, optFns...)
208+
if err != nil {
209+
return nil, err
210+
}
211+
return output, nil
212+
}
213+
214+
func getAwsAccountId(sdkConfig *aws.Config) (string, error) {
215+
stsClient := sts.NewFromConfig(*sdkConfig)
216+
output, err := stsClient.GetCallerIdentity(context.Background(), &sts.GetCallerIdentityInput{})
217+
if err != nil {
218+
return "", err
219+
}
220+
return *output.Account, nil
221+
}

0 commit comments

Comments
 (0)