昨天我們在server.go的地方會發現,要去調用SSM的地方,因為是一個一個參數去查,參數一多看起來就會很混亂,像下面這樣。
如果我們能一次性地去查詢,應該會好看一點,那我們先回到internal\adapter\ssm
,做一個FindParameters
來提供這個功能。
在看AWS-SDK-GO的source code有提供那些function的時候,有找到GetParameters
方法,既然官方有提供SDK,我們直接利用就好。首先在SSMGetParameterAPI
新增GetParameters
,輸入輸出直接複製官方source code上的格式就好。
// ssm.go
type SSMGetParameterAPI interface {
GetParameter(ctx context.Context,
params *ssm.GetParameterInput,
optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error)
GetParameters(ctx context.Context,
params *ssm.GetParametersInput,
optFns ...func(*ssm.Options)) (*ssm.GetParametersOutput, error)
}
接著寫FindParameters
來封裝調用GetParameters
,並返回一個map[string]string
來存查詢到的結果。
// ssm.go
func (s *SSM) FindParameters(c context.Context, api SSMGetParameterAPI, names []string) (map[string]string, error) {
input := &ssm.GetParametersInput{
Names: names,
}
results, err := api.GetParameters(c, input)
if err != nil {
fmt.Println(err.Error())
return nil, err
}
parameters := make(map[string]string, len(results.Parameters))
for _, parameter := range results.Parameters {
if parameter.Name != nil && parameter.Value != nil {
parameters[*parameter.Name] = *parameter.Value
} else {
fmt.Printf("Warning: Parameter %+v has a nil value\n", parameter)
}
}
return parameters, nil
}
寫好後,我們來寫對應的Unit test,先在這邊實現我們測試用的GetParameters
,依照讀到不同的參數名稱,製作對應的Parameter
,append後返回結果。
// ssm_test.go
type SSMGetParameterImpl struct{}
func (dt SSMGetParameterImpl) GetParameters(ctx context.Context,
params *ssm.GetParametersInput,
optFns ...func(*ssm.Options)) (*ssm.GetParametersOutput, error) {
var parameters []types.Parameter
for _, name := range params.Names {
if name == "secret-name" {
parameter := &types.Parameter{
Name: aws.String(name),
Value: aws.String("secret-value"),
}
parameters = append(parameters, *parameter)
}
if name == "token-name" {
parameter := &types.Parameter{
Name: aws.String(name),
Value: aws.String("token-value"),
}
parameters = append(parameters, *parameter)
}
}
output := &ssm.GetParametersOutput{
Parameters: parameters,
}
return output, nil
}
下一步就可以正式寫TestFindParameters
,並注入SSMGetParameterImpl
到FindParameters
,讓FindParameters
會調用到我們上面實現的GetParameters
。BTW,我們透過testify套件來做檢核,所以要記得import "github.com/stretchr/testify/assert"
。
func TestFindParameters(t *testing.T) {
thisTime := time.Now()
nowString := thisTime.Format("2006-01-02 15:04:05 Monday")
t.Log("Starting unit test at " + nowString)
// Creating an instance of the SSMGetParameterImpl
api := &SSMGetParameterImpl{}
// Mocking the configuration
err := populateConfiguration(t)
assert.NoError(t, err, "Error populating configuration")
// Setting up the testify assertion instance
// List of parameter names to fetch
paramNames := []string{
globalConfig.MockChannelSecret,
globalConfig.MockChannelAccessToken,
}
// Testing FindParameters
respParameters, err := testSSM.FindParameters(context.Background(), *api, paramNames)
assert.NoError(t, err, "Error fetching parameters")
// Asserting on the response
expectedValues := map[string]string{
globalConfig.MockChannelSecret: "secret-value",
globalConfig.MockChannelAccessToken: "token-value",
}
for paramName, expectedValue := range expectedValues {
assert.Equal(t, expectedValue, respParameters[paramName], "Unexpected value for parameter: "+paramName)
}
}
完整測試程式碼如下
package ssm
import (
"context"
"encoding/json"
"errors"
"os"
"testing"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ssm"
"github.com/aws/aws-sdk-go-v2/service/ssm/types"
"github.com/stretchr/testify/assert"
)
type SSMGetParameterImpl struct{}
func (dt SSMGetParameterImpl) GetParameter(ctx context.Context,
params *ssm.GetParameterInput,
optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) {
var parameter *types.Parameter
if *params.Name == "secret-name" {
parameter = &types.Parameter{Value: aws.String("secret-value")}
}
if *params.Name == "token-name" {
parameter = &types.Parameter{Value: aws.String("token-value")}
}
output := &ssm.GetParameterOutput{
Parameter: parameter,
}
return output, nil
}
func (dt SSMGetParameterImpl) GetParameters(ctx context.Context,
params *ssm.GetParametersInput,
optFns ...func(*ssm.Options)) (*ssm.GetParametersOutput, error) {
var parameters []types.Parameter
for _, name := range params.Names {
if name == "secret-name" {
parameter := &types.Parameter{
Name: aws.String(name),
Value: aws.String("secret-value"),
}
parameters = append(parameters, *parameter)
}
if name == "token-name" {
parameter := &types.Parameter{
Name: aws.String(name),
Value: aws.String("token-value"),
}
parameters = append(parameters, *parameter)
}
}
output := &ssm.GetParametersOutput{
Parameters: parameters,
}
return output, nil
}
type Config struct {
MockChannelSecret string `json:"MockChannelSecret"`
MockChannelAccessToken string `json:"MockChannelAccessToken"`
}
var configFileName = "config.json"
var globalConfig Config
func populateConfiguration(t *testing.T) error {
content, err := os.ReadFile(configFileName)
if err != nil {
return err
}
text := string(content)
err = json.Unmarshal([]byte(text), &globalConfig)
if err != nil {
return err
}
if globalConfig.MockChannelSecret == "" {
msg := "You must supply a value for MockChannelSecret in " + configFileName
return errors.New(msg)
}
if globalConfig.MockChannelAccessToken == "" {
msg := "You must supply a value for MockChannelAccessToken in " + configFileName
return errors.New(msg)
}
return nil
}
func TestFindParameter(t *testing.T) {
thisTime := time.Now()
nowString := thisTime.Format("2006-01-02 15:04:05 Monday")
t.Log("Starting unit test at " + nowString)
err := populateConfiguration(t)
if err != nil {
t.Fatal(err)
}
api := &SSMGetParameterImpl{}
respSecret, err := testSSM.FindParameter(context.Background(), *api, globalConfig.MockChannelSecret)
assert.NoError(t, err, "Error fetching MockChannelSecret")
t.Log("MockChannelSecret value: " + respSecret)
assert.Equal(t, "secret-value", respSecret, "Unexpected value for MockChannelSecret")
respToken, err := testSSM.FindParameter(context.Background(), *api, globalConfig.MockChannelAccessToken)
assert.NoError(t, err, "Error fetching MockChannelAccessToken")
t.Log("MockChannelAccessToken value: " + respToken)
assert.Equal(t, "token-value", respToken, "Unexpected value for MockChannelAccessToken")
}
func TestFindParameters(t *testing.T) {
thisTime := time.Now()
nowString := thisTime.Format("2006-01-02 15:04:05 Monday")
t.Log("Starting unit test at " + nowString)
// Creating an instance of the SSMGetParameterImpl
api := &SSMGetParameterImpl{}
// Mocking the configuration
err := populateConfiguration(t)
assert.NoError(t, err, "Error populating configuration")
// Setting up the testify assertion instance
// List of parameter names to fetch
paramNames := []string{
globalConfig.MockChannelSecret,
globalConfig.MockChannelAccessToken,
}
// Testing FindParameters
respParameters, err := testSSM.FindParameters(context.Background(), *api, paramNames)
assert.NoError(t, err, "Error fetching parameters")
// Asserting on the response
expectedValues := map[string]string{
globalConfig.MockChannelSecret: "secret-value",
globalConfig.MockChannelAccessToken: "token-value",
}
for paramName, expectedValue := range expectedValues {
assert.Equal(t, expectedValue, respParameters[paramName], "Unexpected value for parameter: "+paramName)
}
}
var testSSM *SSM
func TestMain(m *testing.M) {
testSSM = NewSSM()
os.Exit(m.Run())
}
最後,我們就可以把落落長的NewGinLambda()
改成下面這樣,透過FindParameters
一次取得參數,並且透過mustGetValue
來把值取出來,這樣可以確保一定有找到參數才能開始初始化。
func mustGetValue(parameters map[string]string, key string) string {
value, ok := parameters[key]
if !ok {
log.Fatalf("%s not found. Exiting...\n", key)
}
return value
}
func NewGinLambda() *ginadapter.GinLambda {
rootCtx, _ := context.WithCancel(context.Background()) //nolint
ssmsvc := ssm.NewSSM()
parameterNames := []string{
"XXX_CHANNEL_SECRET",
"XXX_CHANNEL_ACCESS_TOKEN",
"GOOGLE_CLIENT_ID",
"GOOGLE_CLIENT_SECRET",
"REDIRECT_URL",
}
parameters, err := ssmsvc.FindParameters(rootCtx, ssmsvc.Client, parameterNames)
if err != nil {
log.Println(err)
}
lineSecret := mustGetValue(parameters, "XXX_CHANNEL_SECRET")
lineAccessToken := mustGetValue(parameters, "XXX_CHANNEL_ACCESS_TOKEN")
googleClientID := mustGetValue(parameters, "GOOGLE_CLIENT_ID")
googleClientSecret := mustGetValue(parameters, "GOOGLE_CLIENT_SECRET")
redirectURL := mustGetValue(parameters, "REDIRECT_URL")
lineClientLambda, err := linebot.New(lineSecret, lineAccessToken)
if err != nil {
log.Fatal(err)
}
log.Println("LineBot Create Success")
db := dynamodb.NewTableBasics("google-oauth")
oauth := google.NewGoogleOAuth(googleClientID, googleClientSecret, redirectURL)
app := app.NewApplication(rootCtx, db, oauth, lineClientLambda)
ginRouter := initRouter(rootCtx, app)
return ginadapter.New(ginRouter)
}
那今天就先到這邊,颱風天大家要注意安全,明天見囉~