Series — Flutter CI/CD, ตอน: Autodeployment แบบเท่ห์ๆ

Amorn Apichattanakul
5 min readFeb 5, 2021

--

บทความนี้ก็จะเป็น series สุดท้ายของ Flutter CI/CD แล้วครับ ซึ่งตอนนี้ถ้าติดตามมาทุกคนก็จะสามารถ deploy ขึ้นไปเทสบน Firebase Distribution และ TestFlight ได้เรียบร้อยแล้ว ครั้งนี้ผมมาแชร์ bash script ที่จะได้กดสั่งครั้งเดียว ก็ไปนั่งชิวรอมัน deploy ได้

อันนี้คือ Script ที่ผมใช้ครับผม เดี๋ยวขออธิบายเป็นส่วนๆ หรือจะไปโหลดได้ที่ url ด้านล่างครับ

https://raw.githubusercontent.com/theamorn/deploy-flutter/main/deploy.sh

save script ทั้งหมดนี้ไว้ที่ root ของ project Flutter นะครับ วิธีใช้ก็เพียงแค่ run bash command ผมตั้งไว้ชื่อว่า deploy.sh ครับ แล้วก็ใช้คำสั่งด้านล่าง

bash deploy.sh

set -e คือการสั่งว่าถ้า error จุดไหน ให้หยุดทำงานครับ

แล้วผมก็เข้าไปที่ folder ios ด้วย cd ios แล้วใช้คำสั่ง fastlane buildNumber

lane :buildNumber do
lastNum = latest_testflight_build_number + 1
filename = "../../pubspec.yaml"
outdata = File.read(filename).gsub(/version: \d+\.\d+\.\d+\+\d+/, "version: 0.1.0+#{lastNum}")
File.open(filename, 'w') do |out|
out << outdata
end
end

ตามคำสั่งด้านบนใน Fastfile ของ project iOS นะครับ

วิธีทำก็คือผมไปอ่านค่า build ล่าสุดที่ TestFlight นำค่าที่ได้ไปบวก 1 เสร็จแล้วก็ไปหาอ่าน file ที่ชื่อว่า pubspec.yaml แล้วใช้ regular expresssion หาคำว่า version: xxxxx แล้วแทนด้วย version: 0.1.0 (อันนี้แล้วแต่ว่าคนอยากจะกำหนดเป็นเลขอะไร) แล้วตามด้วย lastNum ที่ได้มา เสร็จแล้วก็เขียน file ทับลงไปครับ

มันจะทำให้ version: 1.0.0+100 กลายเป็น version: 0.1.0+50 ครับ ถ้ามันอ่านเลข TestFlight ได้ที่ 49 นะ

ทำไมถึงใช้เลขจาก TestFlight เพราะว่า fastlane ไม่มีวิธีอ่านเลขจาก Firebase Dsitribution ครับ แล้วเลข TestFlight มันการันตีได้ว่าไม่ซ้ำ ตัว Firebase Distribution อนุญาตให้ซ้ำได้

เสร็จแล้วก็จะ cd .. กลับไปที่ root แล้วสั่ง Flutter clean 1 ครั้ง เพื่อความการันตีว่าจะได้ build ใหม่จริงๆ

เสร็จแล้วก็จะรัน command ตามที่เคยตั้งค่าไว้แล้ว ใน series Android และ iOS จากบทความเก่าๆที่เขียนไป

ผมให้รันของ Android ก่อน แล้วค่อยส่งของ iOS นะครับ เพราะว่า Android มัน build เร็วกว่า จะได้ไม่เป็นคอขวดเวลาส่งงาน แล้วก็มีรัน pararrel ตรงหนึงคือ

fastlane beta &

เป็นการสั่งว่า command นี้ทำงานไป แล้วก็ที่เหลือก็ทำงานต่อ ถ้าไม่สั่งแบบนี้มันจะต้องรอส่ง Android app ให้เสร็จก่อน iOS ถึงจะเริ่ม Build ครับ

ที่เหลือก็ตรงไปตรงมาจนส่ง iOS/Android สำเร็จไปที่เรียบร้อยครับ การ deploy ด้วย script ก็จะจบเท่านี้ครับ

ต่อจากนี้เป็นส่วนเสริมที่ไม่จำเป็นต้องทำ แต่มีไว้ก็ดี

ผมไปหาอ่านมาว่าทำยังไงถึงจะอ่านค่า yaml ได้ จริงๆมันมี brew อยู่หลายๆตัว แต่ผมเลือกแบบนี้เพราะว่าอยากให้มันทำงานบน Cloud CI/CD ได้ บางทีเราไม่สามารถ install addon อย่างอื่นๆได้ ก็เลยมาด้วยวิธีนี้ อันนี้ผมก็ไปหามาจาก StackOverFlow เหมือนกันว่าทำยังไง ก็ได้ออกมาแบบนี้ครับ

function parse_yaml เอาไว้อ่าน yaml file แล้วก็ parse ออกมาให้รูปแบบตัวแปรที่

eval $(parse_yaml pubspec.yaml)

เสร็จแล้วเราจะได้ตัวแปรที่ชื่อว่า $version จริงๆมีตัวอื่นอีกเยอะแยะ แต่เราสนใจแค่ตัวนี้ครับ

pubspecVersion=$version
# Replace + with -
versionNumber=${pubspecVersion/+/-}

เอาตัวแปร version ใส่เข้าไปใน pubspecVersion (จริงๆไม่จำเป็นแต่ผมชอบเอาไปใส่ตัวแปรก่อนใช้) แล้วก็ใช้การ replace จาก + ให้เป็นลบ

ตอนแรกเราจะอ่านได้ว่า 0.1.0+50 ก็จะแก้เป็น 0.1.0-50

เพราะว่า git tag กับ version ใน jira ไม่ support + ครับ ผมก็เลยต้องมาเพิ่มส่วนนี้

ต่อไปเมื่อเรา deploy เรียบร้อยแล้วก็แจ้ง Tester หรือคนอื่นๆซะหน่อย ก็ออกมา 2 รูปแบบ คือ Slack Bot กับ LineBot

ใครสะดวกใช้แบบไหนก็ได้ครับ แต่ผมจะแจ้ง 2 ที่ เพราะ Slack มันใช้ยากสำหรับ End User เราจะแจ้งลูกค้าว่า คุณครับ กรุณาสมัคร Slack แล้วเราจะ invite มาครับ มันก็ใช่เรื่อง ก็เลยจะมี 2 options ไว้ สำหรับ non-tech ครับ ที่อยากรับข้อมูลผ่าน Line

วิธีการทำ SlackBot กับ LineBot ลองไปหาดูนะครับ ผมจะไม่ได้พูดถึงในนี้

ก็ให้แทนค่า XXXXX ด้วย credential ของตัวเองที่ได้มาครับ wording ปรับได้นะครับ ปรับที่ “text” field

หลังจากเราแจ้งเตือน user แล้ว เราก็มาใส่ Git Tag กันซะหน่อย อันนี้ผมใช้ GitLab นะครับ ถ้าใครใช้ Github อาจจะต้องหาวิธีอื่นมาแทนคับ

ให้เอา Gitlab access Token มาใส่ที่ access_token

วิธีทำตามด้านบนครับ

แล้วก็ไปเอา gitlabID มาใส่ ก็คือ project repo ของเรานั้นเอง วิธีเอาก็คือไปที่ Repo ของเรา แล้วเลือกที่ Project Details ครับ ก็จะเจอ ProjectID ของเรา เอามาใส่ที่ gitLabID ได้เลยครับ

แล้วทางผมก็ใช้ Jira ในการทำ Ticket Management (คิดว่าน่าจะ 60–70% + น่าจะใช้ Jira กันนะครับ) ถ้าใช้อันอื่นๆคงต้องหาเองละครับ

เราก็ใช้คำสั่ง add git tag version 0.1.0–50 ที่ได้มาลงใน GitLab

Tag เอาไว้ดีนะครับ จะได้รู้ว่า version ที่เราส่งไปมันอยู่ที่ตรงไหน จะได้ตามมาเช็คถูก

"https://gitlab.com/api/v4/projects/$gitlabID/repository/tags?tag_name=$versionNumber&ref=master"

ผมจะ tag ไว้ที่ master นะครับ ตรง ref ใครอยากเปลี่ยนก็แก้ตรงนี้ได้

เสร็จปุ๊บเราก็จะไป add version ใน Jira

curl --silent POST --url 'https://XXXXX.atlassian.net/rest/api/3/version' \
--header 'Authorization: Basic XXXXX' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{"archived": false, "name": "'"$versionNumber"'", "projectId": "'$jiraID'", "released": false}'

XXXX เปลี่ยนตามชื่อทีม Jira ของคุณนะครับ วิธีเอา Header ของ Jira ก็ตามไปที่นี้ได้เลย

code จะเอา 0.1.0–50 ใส่เข้าไปใน Project Jira ของคุณครับ

ขั้นตอนต่อไปเอาจะ assign ticket ทุกอัน ใส่ fix version เข้าไปที่ตัวล่าสุด แล้วเลื่อน ticket ไปที่ lane ที่ต้องการ พร้อมกับ Assign กลับไปหาคนที่ create ticket ให้เราครับ

อันนี้จะเป็น for loop ใน curl ครับ

อธิบายที่ละส่วน

for idParams in $(curl --request GET \
--url 'https://xxxxx.atlassian.net/rest/api/3/search?jql=xxxxxx' \
--header 'Authorization: Basic xxxxx' \
--header 'Accept: application/json' | jq -r '.issues[] | (.fields.creator.accountId + "," + .id)'); do
params=(${idParams//,/ })
accountID=${params[0]}
issueID=${params[1]}

อันนี้คือ for loop ซึ่งผมหาวิธีเอาแต่ละค่ามาไม่เป็น 555 ผมก็เลยเอา accountID + id เป็น String ครับ

มันจะออกมาเป็น yyyyy,xxxxxx

แล้วผมก็มาตัด String เพื่อเอาค่าแรกคือ คนสร้าง ID คือใคร แล้วก็เอา Ticket ID วอะไรมา จะได้ accountID กับ issueID ครับ

ส่วนค่า JQL เราสามารถไป copy ได้ที่ Project ของ JIRA โดยการไปที่ Issue ที่เมนูด้านซ้าย แล้วก็ไป All issues ครับ จะได้ url นี้มา

https://xxxxxx.atlassian.net/jira/software/c/projects/yyyy/issues/?filter=allissues

xxxx คือชื่อทีมคุณ yyyy คุณชื่อ board project แล้วก็ไปเลือก Search Issues ครับ จะได้หน้าตามเหมือนด้านล่าง

ทีนี้ให้เราเลือก filter ต่างๆได้เลยครับ อย่างของผมก็จะได้ Board ชื่อ NEO โดยปกติก็จะเลือก Lane ที่เรากอง ticket ที่ทำเสร็จไว้ สมมตนะครับ ผมจะได้ JQL แบบนี้

project%20%3D%20NEO%20AND%20status%20%3D%20REVIEWED

ก็คือจะบอกว่าเอา Board ชื่อ NEO และหา Ticket ที่ Status เป็น “REVIEWED” (Status ที่ทำขึ้นมาเอง เพื่อบอกว่า Ticket ทั้งหมดใน Lane นี้คือ Reviewed เรียบร้อยแล้ว Merged เรียบร้อยแล้วครับ ให้เอาด้านบนไปแทนที่ค้นหาเลยครับ จะได้ออกมาเป็น

for idParams in $(curl --request GET \
--url 'https://xxxxx.atlassian.net/rest/api/3/search?jql=project%20%3D%20NEO%20AND%20status%20%3D%20REVIEWED' \
--header 'Authorization: Basic xxxxx' \
--header 'Accept: application/json' | jq -r '.issues[] | (.fields.creator.accountId + "," + .id)'); do
params=(${idParams//,/ })
accountID=${params[0]}
issueID=${params[1]}

แบบด้านบนเลยครับ

ต่อไปจะเป็น Part วนลูบด้านในครับ

Assign — IssueID นี้กลับไปที่ accountID ซึ่ง AccountID เราเอาคนที่สร้าง ticket มาใส่ครับ ก็สรุปจะ Assign กลับไปหาคนที่สร้าง Ticket

Update — จะ update ticket จาก issueID ให้ใส่ fixVersion 0.1.0–50 ลงไปครับ ทาง Tester จะได้รับทราบว่าอันนี้แก้ไปที่ Version อะไร

Move — คือการย้าย ticket issueID ไป Lane ที่ถูกต้องซึ่งผมจะได้เลขมาคือ lane 61 ครับ id นี้มาจากไหน?

ให้ใช้

curl --request GET \
--url 'https://xxxxx.atlassian.net/rest/api/3/issue/12918/transitions' \
--header 'Authorization: Basic xxxxx' \
--header 'Accept: application/json'

นี้เพื่อหาค่าว่า status ที่เราต้องการคือ id อะไร เมื่อได้เสร็จก็เอา id นั้นมาใส่ได้ครับ

12918 คือ issueID ที่จำลองขึ้นมานะครับ เอา issue ไหนก็ได้เป็นตัวตั้ง เพราะว่าบาง workflow issue แต่ละประเภทจะวิ่งไป lane ที่ต่างกันครับ แต่ workflow ของผมคือ ไป lane เดียวกัน ผมก็เลย issueID ไหนก็ได้มาลองยิงดู

ถ้าเอาไปยิง curl ก็จะได้ตามนี้ครับ ผมก็เอา id 81 ไปใส่ เพื่อให้มันย้ายไป Status ที่ชื่อว่า Ready for Demo

เรียบร้อยครับผม อันนี้ก็เอา script มาแชร์กันให้นะครับ ลองเอาไปปรับเปลี่ยนให้เหมาะกับทีมคุณดูละกันครับ

จบเรียบร้อยครับ กับ Series Flutter CI/CD

Series ต่อไปที่คิดมาจะทำก็จะเป็น Series: Security กับ Flutter ครับ วิธีการเช็ตต่างๆ ที่ทำให้ iOS/Android ผ่าน Pen test!!!! อันนี้น่าจะเป็น Series ยาวๆเลยครับ อาจจะมีสัก 4 ตอนเลย ส่วนเรื่องการใช้งานการเขียนหรืออะไรอื่นๆ ผมมองว่าคนอื่นๆน่าจะเขียนไปหมดแล้วครับ ผมก็เลยคิดว่าเราก็มีประสบการณ์ทาง Security มานิดหน่อยนี่หน่า น่าจะเอาส่วนที่คนอื่นยังไม่เขียนมาเขียนดีกว่า จะได้แชร์ๆกันหลากหลาย

ขอบคุณครับผม

--

--

Amorn Apichattanakul
Amorn Apichattanakul

Written by Amorn Apichattanakul

Google Developer Expert for Flutter & Dart | Senior Flutter/iOS Software Engineer @ KBTG Join us for more info https://www.facebook.com/groups/1565880160729817

No responses yet