iT邦幫忙

2023 iThome 鐵人賽

DAY 19
0

昨天我們在server.go的地方會發現,要去調用SSM的地方,因為是一個一個參數去查,參數一多看起來就會很混亂,像下面這樣。

https://ithelp.ithome.com.tw/upload/images/20231004/201159902qktBxMqVF.png

如果我們能一次性地去查詢,應該會好看一點,那我們先回到internal\adapter\ssm,做一個FindParameters來提供這個功能。

  1. 在看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)
    }
    
  2. 接著寫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
    }
    
  3. 寫好後,我們來寫對應的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
    }
    
  4. 下一步就可以正式寫TestFindParameters,並注入SSMGetParameterImplFindParameters,讓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)
    	}
    
    }
    
  5. 完整測試程式碼如下

    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())
    }
    
  6. 最後,我們就可以把落落長的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)
    }
    

那今天就先到這邊,颱風天大家要注意安全,明天見囉~


上一篇
Day18 GoogleDrive API 04
下一篇
Day20 Login Service - OAuth Token存到Dynamodb
系列文
Golang LineBot X GoogleDrive:LINE有各種限制!? 那就丟上Drive吧!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
MissterHao
iT邦新手 5 級 ‧ 2023-10-05 01:26:33

真貼心!

必須的><

我要留言

立即登入留言