Author: Sam

  • Triển khai CI/CD cho iOS – Appstore Distribution

    Triển khai CI/CD cho iOS – Appstore Distribution

    The most powerful tool we have as developers is automation.

    Scott Hanselman

    Overview

    Tiếp tục với series triển khai CD cho iOS, hôm nay chúng ta sẽ tìm hiểu cách chạy CD để đẩy một ứng dụng lên App Store Connect một cách tự động.

    Có rất nhiều doanh nghiệp, dự án chọn App Store Connect & TestFlight làm nền tảng để Test sản phẩm, đăc biệt với các sản phẩm gần thời điểm lên App Store. Thêm một sự tiện lợi nữa đến từ TestFlight, khi có phiên bản mới được upload lên sẽ có thông báo về cho các thiết bị cài đặt TestFlight. Vậy nếu triển khai CD để tự động đưa ứng dụng lên App Store Connect, ta cũng sẽ có một hệ thống thông báo tự động khi quá trình hoàn tất mà không cần triển khai thêm gì.

    Quan trọng: Để sử dụng được phương thức này ta cần chú ý một số điểm sau:

    • Tạo sẵn App trên App Store Connect với đúng bundle id
    • Bản build sau bắt buộc phải có version/build number lớn hơn version/build number của bản trước đó

    Distribute và Upload IPA sử dụng Command Line

    Sẽ không có nhiều khác biệt giữa phương thức Archive và Export của phiên bản App Store với các phiên bản khác, ta vẫn sẽ sử dụng các câu lệnh cũ, tuy nhiên phần export sẽ không cần tham số -exportPath:

    xcodebuild archive -archivePath "cicd-test" -scheme cicd-test
    xcodebuild -exportArchive -archivePath "cicd-test.xcarchive" -exportOptionsPlist ExportOptions.plist -allowProvisioningUpdates

    Với chế độ App Store, chúng ta sẽ sử dụng file ExportOptions.plist với nội dung như sau:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    	<key>method</key>
    	<string>app-store</string>
    	<key>destination</key>
    	<string>upload</string>
    </dict>
    </plist>

    Cần thêm có key destination với value upload để cấu hình việc tự động upload ipa thay vì export file.

    Cấu hình .gitlab-ci.yml cho GitlabCI

    Với phương thức này, ta chỉ cần đơn giản cấu hình file .gitlab-ci.yml như sau:

    stages:
      - deploy
    
    deploy_appstore:
      stage: deploy
      script:
        - echo 'Hello bitches, welcome to lazy boys world!'
        - echo 'Just commit code, serve yourself a cup of coffee. Let gitlab build your app!'
        - xcodebuild archive -archivePath "cicd-test" -scheme cicd-test
        - xcodebuild -exportArchive  -archivePath "cicd-test.xcarchive" -exportPath "ipa" -exportOptionsPlist ExportOptions.plist -allowProvisioningUpdates
      tags:
        - main

    Kết quả log của Job khi upload thành công trên Gitlab sẽ trông như sau

    Log của quá trình upload App lên App Store Connect

    Như vậy là đã hoàn thành việc cấu hình CD upload ứng dụng lên App Store Connect. Ở bài viết tiếp theo, chúng ta sẽ cùng tìm hiểu cách triển khai CD cho ứng dụng iOS theo phương thức OTA với các Website đang Deploy trên AWS S3 😀

  • Triển khai CI/CD cho iOS – In house Distribution với DeployGate

    Triển khai CI/CD cho iOS – In house Distribution với DeployGate

    Một ngày đẹp trời, anh ấy nhận được một cái mail giới thiệu anh là chuyên gia về tài khoản Apple… đó chính ngày định mệnh mở ra con đường Enterprise. Many thanks anh Vũ Béo, người nhanh tay mang ánh sáng về và đặt những bước chân đầu tiên.

    Sam

    Overview

    DeployGate hỗ trợ rất nhiều chế độ Distribution, tuy nhiên để tạo ra sự đa dạng cho series, ở bài viết này chúng ta sẽ sử dụng Enterprise (In house) kết hợp với DeployGate để tạo thành một quy trình CD.

    Nói thêm một chút về DeployGate, đây là một nền tảng hỗ trợ phân phối ứng dụng di động (iOS & Android). Người dùng có thể upload app package (ipa & apk) lên và chia sẻ link cài đặt cho người khác. Sử dụng DeployGate, Tester sẽ không cần phải cài đặt ứng dụng trực tiếp với ipa hay apk file, và cũng không cần sử dụng Window hoặc MacOS để cài đặt. DeployGate cũng giảm bớt sự phụ thuộc của người dùng trong quá trình test vào Test Flight khi thời gian process một số ứng dụng có thể lên tới hàng chục giờ đồng hồ.

    Bài viết sẽ gồm có 3 phẩn:

    1. Distribute IPA sử dụng Command Line
    2. Cấu hình DeployGate để upload IPA bằng Command Line
    3. Cấu hình file .gitlab-ci.yml cho GitlabCI

    Distribute IPA sử dụng Command Line

    Bước đầu tiên của quá trình vẫn là tạo ra được file IPA để upload lên Deploy Gate. Như ở bài trước đã hướng dẫn, ta sẽ sử dụng các câu lệnh sau để thực hiện tạo ra file IPA.

    xcodebuild archive -archivePath "cicd-test" -scheme cicd-test
    xcodebuild -exportArchive  -archivePath "cicd-test.xcarchive" -exportPath "ipa" -exportOptionsPlist ExportOptions.plist -allowProvisioningUpdates

    Với chế độ In House, chúng ta sẽ sử dụng file ExportOptions.plist với nội dung như sau:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    	<key>method</key>
    	<string>enterprise</string>
    </dict>
    </plist>

    Sau khi export xong, chúng ta sẽ có một file IPA với đường dẫn tương đối so với vị trí của runner như sau:

    ipa/cicd-test.ipa

    Việc tiếp theo sẽ là upload file IPA lên DeployGate.

    Cấu hình DeployGate để upload IPA bằng Command Line

    Đầu tiên chúng ta cần upload ít nhất một bản IPA lên Deploy Gate, sau đó bật Distribution lên bằng cách ấn vào nút Add a link for sharing trong ảnh sau:

    Ấn vào button [+ Add a link for sharing] phía bên phải

    Sau đó, trang web sẽ điều hướng đến Distribution Page, đừng hoảng, ta chỉ cần copy đường dẫn của trang đó là được. Đường dẫn sẽ có dạng như sau:

    https://deploygate.com/distributions/818c15431b824aac1763f074931ecacaed2a03d5
    DISTRIBUTION_KEY = 818c15431b824aac1763f074931ecacaed2a03d5

    Ta sẽ lưu key phía sau của đường dẫn lại, tạm gọi là DISTRIBUTION_KEY như trên.

    Tiếp đó ta quay lại Dashboard của app như trong ảnh trên và lấy thông tin user từ đường dẫn như sau:

    https://deploygate.com/users/gstdn_test/platforms/ios/apps/com.magz.techover.ios
    USER = gstdn_test
    USER_API_HOST = https://deploygate.com/api/users/gstdn_test/apps

    Cuối cùng của việc cấu hình và thu thập thông tin lên Deploy Gate, ta vào mục Account Setting -> Profile và copy API Key ở dưới cùng của page.

    Copy API Key này nhé bạn

    Ta sẽ có một bộ các thông tin sau:

    USER_API_HOST = https://deploygate.com/api/users/gstdn_test/apps
    DISTRIBUTION_KEY = 818c15431b824aac1763f074931ecacaed2a03d5
    API_KEY = 246147bb-3ab7-4cc8-8fbe-ahihidongoc

    Tiếp đó, ta chỉ cần thực hiện upload IPA file lên Deploy Gate sử dụng các thông tin trên với Command sau:

    Curl -H "Authorization: token $API_KEY" -F "file=@ipa/cicd-test.ipa" -F "message=New distribution" -F "distribution_key=$DISTRIBUTION_KEY" -F "release_note=Release Note" "$USER_API_HOST"

    Response trả về sẽ có dạng Json như sau, nếu error = false thì tức là quá trình upload thành công.

    Response trả về của Curl upload IPA lên Deploy Gate

    Refresh lại Dashboard của app ta sẽ thấy có một bản build mới được upload lên với message “New distribution” như ảnh sau:

    Bản build đã lên với message đi kèm

    Tiếp tục qua trang Update Distribution, ta sẽ thấy bản build mới upload sẽ tự động được cập nhật thành phiên bản được phân phối với Release Notes là “Release Note”.

    Bản build vừa upload được tự động phân phối với Release Note mới

    Như vậy là ta đã hoàn thành việc upload và cập nhật bản build trên Deploy Gate một cách tự động. Tiếp đến là cấu hình các câu lệnh trên vào file .gitlab-ci.yml.

    Cấu hình .gitlab-ci.yml cho GitlabCI

    Rồi, lại tới công chuyện tiếp! Đầu tiên ta cần ném các thông tin Key của Deploy Gate vào mục Variables thay vì setting cứng trong file.

    Luôn đặt các thông tin key, môi trường vào Variables

    Tiếp đến, nội dung của file .gitlab-ci.yml cơ bản sẽ như sau:

    stages:
      - build
    
    build_project:
      stage: build
      script:
        - echo 'Hello bitches, welcome to lazy boys world!'
        - echo 'Just commit code, serve yourself a cup of coffee. Let gitlab build your app!'
        - xcodebuild archive -archivePath "cicd-test" -scheme cicd-test
        - xcodebuild -exportArchive  -archivePath "cicd-test.xcarchive" -exportPath "ipa" -exportOptionsPlist ExportOptions.plist -allowProvisioningUpdates
        - Curl -H "Authorization: token ${API_KEY}" -F "file=@ipa/cicd-test.ipa" -F "message=New distribution" -F "distribution_key=${DISTRIBUTION_KEY}" -F "release_note=Release Note" "${USER_API_HOST}"
      tags:
        - main

    Vậy là xong, sau khi code được commit/merge vào main, GitlabCI sẽ tự động chạy Jobs build IPA và đẩy lên DeployGate. Hẹn gặp lại các bạn ở bài viết tiếp theo!

    P/S: Sẽ rất tiện lợi nếu có thể cấu hình để tuỳ chỉnh Upload Message, Release Note để ghi các thay đổi cho Tester hay thông báo khi có bản build mới lên Deploy Gate. Nên mình sẽ dành riêng một bài trong series để hướng dẫn cách cấu hình các tuỳ chỉnh này.

  • Triển khai CI/CD cho iOS – GitlabCI Artifact

    Triển khai CI/CD cho iOS – GitlabCI Artifact

    Ngày đông giá rét, thay vì ngồi dài cổ đợi ae commit code lên để build cho Tester. Bạn có thể đơn giản add Tester vào Gitlab, cài một con CD Artifact đơn giản và ném vào mặt nó phần việc nhàm chán kia. Còn bạn có thể nhàn nhã mò ra Highlands, nhâm nhi cốc cafe nóng và xem cơm chó của ngày Noel buồn…

    Sam, gửi Cương Good, Anh Mẹc và Very Good Team

    Overview

    Tiếp tục với series CI/CD cho iOS, hôm nay chúng ta sẽ chuyển sang chuyên mục CD với phương thức Deploy đơn giản nhất – GitlabCI Artifact.

    Vì đây là bài viết đầu tiên liên quan đến CD, chúng ta sẽ cần đề cập đến phương thức Archive và Distribute ứng dụng sử dụng command line.

    Distribute IPA sử dụng Command Line

    Để triển khai CD, mọi công việc đều phải thực hiện thông qua Shell Command, do đó từ các công việc Build, Test, Archive hay Distribute IPA đều phải thực hiện bằng các câu lệnh. Rất may, Apple cung cấp sẵn một bộ Command Line Tools được tích hợp kèm với Xcode, ta có thể sử dụng ngay khi máy đã cài đặt Xcode. Các bạn có thể tham khảo hướng dẫn các command tại đây.

    Đầu tiên, chúng ta sẽ cài đặt setting project sang Automatic Signing, đảm bảo thiết bị chạy runner đã đăng nhập tài khoản hợp lệ và build thành công App bằng Xcode.

    Setting Automatically trong Xcode

    Sau khi thành công, ta sẽ thử build project bằng câu lệnh với cú pháp:

    xcodebuild build -scheme cicd-test

    Với tham số -scheme là chỉ định scheme/target để build.

    Build thành công Project trên Terminal

    Sau khi thành công, chứng tỏ là project đã được setting đúng cách, chúng ta sẽ thực hiện archive App bằng câu lệnh sau:

    xcodebuild archive -archivePath "cicd-test" -scheme cicd-test

    Với tham số -archivePath chỉ định tên và đường dẫn export ra .xcarchive file.

    Sau khi chạy xong, chúng ta sẽ thấy có một file được export ra với tên cicd-test.xcarchive trông như ảnh sau:

    Output của quá trình archive app

    Sau khi đã có .xcarchive file, chúng ta cần thực hiện export ra IPA file. Để export được IPA file, chúng ta cần một file ExportOptions.plist, ở đây chúng ta sẽ export bằng chế độ adhoc, file sẽ có nội dung như sau:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    	<key>method</key>
    	<string>ad-hoc</string>
    </dict>
    </plist>

    Trong trường hợp IPA cần export ở các chế độ khác, ta sẽ thay ad-hoc bằng các keyword tương ứng sau:

    • Development: development
    • Adhoc: ad-hoc
    • AppStore: app-store
    • Enterprise: enterprise

    Sau đó, thực hiện export IPA bằng câu lệnh sau:

    xcodebuild -exportArchive  -archivePath "cicd-test.xcarchive" -exportPath "ipa" -exportOptionsPlist ExportOptions.plist -allowProvisioningUpdates

    Với các tham số sau đây:

    • -exportPath: Chỉ định thư mục sẽ export ra file IPA
    • exportOptionsPlist: Chỉ định file chưa các config export
    • -allowProvisioningUpdates: Cần phải có nếu Project setting chế độ Automatic Signing. Không cần sử dụng nếu setting manual signing.

    Sau khi export xong, chúng ta sẽ nhận được file IPA ở thư mục như hình sau:

    Vị trí file IPA được export ra

    Như vậy là ta đã tạo xong file IPA, tiếp đến sẽ là setting với GitlabCI

    Cấu hình .gitlab-ci.yml cho GitlabCI

    Rồi tới công chuyện, giờ chúng ta chỉ cần chuyển toàn bộ các câu lệnh ở trên vào phần script của file .gitlab-ci.yml và cấu hình để GitlabCI nhận file IPA làm Artifact là xong.

    File .gitlab-ci.yml cơ bản sẽ có nội dung như sau:

    stages:
      - build
    
    build_project:
      stage: build
      script:
        - echo 'Hello bitches, welcome to lazy boys world!'
        - echo 'Just commit code, serve yourself a cup of coffee. Let gitlab build your app!'
        - xcodebuild archive -archivePath "cicd-test" -scheme cicd-test
        - xcodebuild -exportArchive  -archivePath "cicd-test.xcarchive" -exportPath "ipa" -exportOptionsPlist ExportOptions.plist -allowProvisioningUpdates
      tags:
        - main
      artifacts:
        paths:
          - ipa/cicd-test.ipa

    Sau đó, hãy commit các thay đổi lên và chờ đợi thành quả!

    Thành quả của quá trình là đây

    Như bạn thấy trong ảnh trên, phía bên phải của Jobs Detail sẽ có một section để tải về Job Artifacts chứa file IPA có thể cài được. Ở đây, ta chỉ cần ấn nút Download IPA thực hiện cài đặt ứng dụng vào điện thoại.

    Như vậy là ta đã hoàn thành cấu hình CD đơn giản cho iOS với GitlabCI Artifact. Hẹn gặp các bạn ở bài viết tiếp theo.

  • Triển khai CI/CD cho iOS – SonarQube & Blackduck

    Overview

    Tiếp tục với series CI/CD cho iOS, hôm nay chúng ta sẽ triển khai CI với hai nền tảng kiểm tra source code rất nổi tiếng là SonarQube và Blackduck.

    Triển khai CI với Blackduck

    Khác với SonarQube, Blackduck không đánh giá chất lượng mà giúp chúng ta quản trị open source code và source code từ các thư viện được thêm vào. Giúp chúng ta đánh giá và quản lý được các rủi ro về bản quyền, bảo mật khi sử dụng source code có sẵn trên mạng cũng như lưu hành trong cộng đồng.

    Do Blackduck không có Server public như Sonar, nên mình sẽ giả định chúng ta có một Server Blackduck được đặt tại địa chỉ sau:

    https://blackduck.techover.io

    Sau khi truy cập vào, chúng ta sẽ thấy danh sách các project đã có ở Dashboard. Ở đây mình đã tạo sẵn một Project W95.CICD, nếu muốn tạo mới chúng ta sẽ ấn nút Create Project ở góc trên bên phải.

    Dashboard của Blackduck

    Cũng giống như Sonar, để có thể đồng bộ các dữ liệu Scan chúng ta cần có một Token. Để tạo Blackduck Token, chúng ta sẽ vào màn hình quản lý Access Tokens như sau:

    Ấn vào Profile ở góc trên bên phải, chọn My Access Tokens

    Sau khi chuyển đến màn hình quản lý Access Token, chúng ta ấn Create New Access Token và điền thông tin.

    Màn hình quản lý Access Tokens
    Nhập thông tin và ấn Create

    Sau khi có token, chúng ta sẽ lưu lại để sử dụng sau này. Lưu ý, token sẽ không hiển thị lại lần thứ 2 nên hãy copy và lưu lại ngay và luôn nhé. Ví dụ mình sẽ lưu lại token lại dưới đây:

    YWJmYzc5MDMS05N2VjLWFkNGE4ZMS05N2VjLWFkNGE4Z--=/

    Đối với Blackduck, chúng ta cũng sử dụng gần giống như Sonar nhưng thay vì CLI, chúng ta sẽ sử dụng Java Archive, vì vậy hãy đảm bảo bạn đã cài Java trong thiết bị Runner nhé.

    Tải về phiên bản .jar mới nhất của blackduck ở đây

    Sau khi tải xong, chúng ta sẽ giải nén và lưu và một thư mục trong thiết bị Runner, ở đây mình sẽ lưu ở địa chỉ sau:

    /Users/jena/CICD/synopsys-detect/synopsys-detect-6.4.1.jar

    Rồi xong, tới công chuyện luôn!!

    Tiếp đó chúng ta chỉ cần cấu hình file .gitlab-ci.yml như sau:

    stages:
      - Lint
    blackduck-detect:
      stage: Lint
      only:
          - cicd
      script:
        - java -jar /Users/jena/CICD/synopsys-detect/synopsys-detect-6.4.1.jar \
          --blackduck.url=https://blackduck.techover.io \
          --blackduck.api.token=YWJmYzc5MDMS05N2VjLWFkNGE4ZMS05N2VjLWFkNGE4Z--=/ \
          --detect.project.name=W95.CICD
      tags:
        - w95

    Vậy là xong, mỗi khi có commit/merge lên nhánh cicd (hãy thay bằng master/main/develop) thì hệ thống sẽ tự động scan source code và gửi kết quả lên Server.

    Quên mất, còn một bước cuối cùng nữa là bạn có thể che đi các thông tin nhạy cảm trong file cấu hình .gitlab-ci.yml, đề phòng trong trường hợp file cấu hình bị rò rỉ, các thông tin về Server Blackduck cũng như Token cũng sẽ không bị ảnh hưởng. Để làm việc này chúng ta sẽ cấu hình một số thông tin sau vào biến môi trường của Gitlab-CI

    BLACKDUCK_SERVER = https://blackduck.techover.io
    BLACKDUCK_TOKEN = YWJmYzc5MDMS05N2VjLWFkNGE4ZMS05N2VjLWFkNGE4Z--=/

    Và kết quả, file .gitlab-ci.yml sẽ trông như sau:

    stages:
      - Lint
    blackduck-detect:
      stage: Lint
      only:
          - cicd
      script:
        - java -jar /Users/jena/CICD/synopsys-detect/synopsys-detect-6.4.1.jar \
          --blackduck.url=${BLACKDUCK_SERVER} \
          --blackduck.api.token=${BLACKDUCK_TOKEN} \
          --detect.project.name=W95.CICD
      tags:
        - w95

    Rồi xong, tới công chuyện luôn!! Như vậy là chúng ta đã hoàn thành cấu hình CI với Blackduck để scan các lỗi hổng bảo mật, bản quyền. Mặc dù liên quan đến CI còn rất nhiều section như Coverity, Build & Compile nhưng mình xin phép tạm dừng hạng mục CI và chuyển sang CD. Rất mong được các bạn ủng hộ.

  • Triển khai CI/CD cho iOS – SonarQube & Blackduck

    Triển khai CI/CD cho iOS – SonarQube & Blackduck

    Overview

    Tiếp tục với series CI/CD cho iOS, hôm nay chúng ta sẽ triển khai CI với hai nền tảng kiểm tra source code rất nổi tiếng là SonarQube và Blackduck.

    Triển khai CI với SonarQube

    Đôi chút về SonarQube, đây là một nền tảng mã nguồn mở sử dụng để kiểm tra chất lượng của source code, đánh giá các lỗi ở nhiều mức độ và tiêu chí khác nhau. Mục đích cuối cùng là để thống kê và cải thiện chất lượng của source code theo mọi mặt cũng như giúp lập trình viên đánh giá chất lượng của chính mình. Vì vậy, việc sử dụng SonarQube để hỗ trợ quá trình phát triển, nâng cao chất lượng source code luôn được các doanh nghiệp lớn áp dụng.

    Chúng ta sẽ thực hiện CI với SonarQube server ở địa chỉ sau: https://sonarcloud.io, bạn có thể sử dụng tài khoản GitLab của mình để đăng nhập và tạo project ở đây. Sau một vài bước đăng nhập, tạo organization và chọn plan thì chúng ta sẽ đến với step đầu tiên.

    Đối với một số trường hợp có Server riêng để host SonarQube, các bạn hãy truy cập host và sử dụng tài khoản/mật khẩu được cung cấp bởi admin.

    Cấu hình SonarQube

    Việc đầu tiên, chúng ta sẽ cần tạo một project, ở đây mình sẽ tạo một project như hình sau:

    Điền Project Key và Display name sau đó ấn Set Up

    Trong một số trường hợp, bước config tạo Project sẽ do admin tạo, các bạn chỉ cần đăng nhập là xem được các project mình được phân quyền.

    Sau khi ấn Set Up, chúng ta sẽ được suggest 3 lựa chọn, ở đây mình sẽ chọn Manually để có thể sử dụng ở nhiều nền tảng khác nhau (Không chỉ riêng GitLab-CI).

    Lựa chọn Manually

    Tiếp đó chúng ta sẽ chọn các lựa chọn như hình sau:

    Chọn Other(…) -> macOS

    Tiếp đó chúng ta ấn Download để tải CLI của SonarQube về, giải nén và lưu vào một thư mục trong thiết bị runner. Giả sử mình sẽ lưu ở địa chỉ sau:

    /Users/jena/Projects/sonar-scanner/bin

    Mình sẽ thêm thư mục bin vào trong PATH của macOS bằng câu lệnh sau

    export PATH=$PATH:/Users/jena/Projects/sonar-scanner/bin

    Sau đó, bạn có thể chạy lệnh sau để kiểm tra xem cli đã được nhận vào PATH chưa. Sẽ có một số lỗi yêu cầu cấp quyền để chạy CLI, bạn hãy vào System Preference -> Security & Privacy -> Tab General -> Allow Anyway tất cả

    sonar-scanner -v
    Sau khi cli được add vào PATH, bạn có thể chạy bằng lệnh sonar-scanner

    Tiếp đó, chúng ta sẽ vào GitLab và cài đặt biến môi trường SONAR_TOKEN như hình sau

    Tiếp đó, chúng ta sẽ cấu hình CI ở file .gitlab-ci.yml như sau, phần script sẽ được gen cùng với SONAR_TOKEN, các bạn chỉ cần copy và paster vào là được:

    stages:
      - Lint
    sonar-scanner:
      stage: Lint
      only:
          - cicd
      script:
        - sonar-scanner -Dsonar.organization=w95 -Dsonar.projectKey=CICD.iOS -Dsonar.sources=. -Dsonar.host.url=https://sonarcloud.io -Dsonar.branch=master
      tags:
        - w95

    Sau khi commit file .gitlab-ci.yml lên branch cicd, chúng ta sẽ có kết quả như sau:

    Job Sonar Scanner chạy thành công với log như trên
    Màn hình thống kê trên SonarCloud.io cũng sẽ hiển thị các thông số của source code

    Theo như ảnh trên, Quality Gate đang đánh giá Passed tức là source code đạt chất lượng, nhưng thật ra mình scan Starter Project của iOS nên mới không có lỗi, còn code của mình thì lắm lỗi lắm :p

    Như vậy là chúng ta đã hoàn thành bước cấu hình CI sử dụng SonarQube cho một project iOS. Mỗi khi có commit, merge hay sự thay đổi trên branch cicd (bạn sẽ đổi thành master/develop/main) thì hệ thống sẽ tự động chạy CI và đẩy thống kê lên Sonar server. Chúng ta chỉ cần lên đó, tracking các thông số và sửa các lỗi bị cảnh báo là được.

    Do bài viết hơi dài, nên mình sẽ để phần Blackduck sang bài viết sau. Cảm ơn các bạn đã đọc!

    Authors

    LinhNB1

  • Triển khai CI/CD cho iOS – SwiftLint

    Triển khai CI/CD cho iOS – SwiftLint

    Hưởng ứng theo tinh thần của Editor team, mình đóng góp Series này để hưởng ứng Technopedia, không nhằm mục đích dự thi. Mong rằng các kinh nghiệm của mình sẽ giúp ích được cho cộng đồng trong lĩnh vực liên quan.

    Sam

    Để triển khai CI/CD cho một sản phẩm iOS có rất nhiều lựa chọn, chúng ta có thể sử dụng GitLab-CI, Xcode Server, Fastlane, Jenkins, Microsoft App Center, Circle CI, …
    Ở phạm vi bài viết này, chúng ta sẽ đề cập đến một nền tảng được tích hợp với GitLab: GitLab-CI

    Việc triển khai CI/CD cho một dự án iOS Swift bao gồm một số phần sau:

    1. Triển khai CI với SwiftLint
    2. Triển khai CI với SonarQube, Blackduck
    3. Triển khai CD đơn giản với Gitlab-CI Artifact
    4. Triển khai CD In-house với DeployGate.
    5. Triển khai CD OTA Inhouse trên Website với AWS S3 (Static Website Hosting)
    6. Triển khai CD với Appstore Connect

    Cài đặt và khởi tạo Runner

    Đầu tiên, các chúng ta cần cài đặt và khởi tạo Runner cho Gitlab Repo. Mình có viết một bải hướng dẫn ở đây
    Chú ý:
    Sau khi đăng ký runner, nhưng các job vẫn chạy trên container mặc định của GitLab-CI thì hãy chạy các lệnh sau:

    gitlab-runner install
    gitlab-runner start
    gitlab-runner status

    1. Triển khai CI với SwiftLint

    Đầu tiên, chúng ta sẽ đi đến việc cấu hình CI sử dụng SwiftLint để phân tích chất lượng source code.

    Tốt nhất chúng ta sẽ cài đặt và sử dụng Swiftlint tách biệt với source code của dự án như sau

    brew install swiftlint

    Sau khi cài đặt Swiftlint, ta có thể test bằng cách di chuyển vào thư mục root của source code và chạy lệnh

    swiftlint

    Kết quả lint source code sẽ hiển thị như sau, ví dụ ở đây ta có 16 lỗi.

    Sau đó, chúng ta cấu hình file .gitlab-ci.yml để chạy lint trên branch cicd như sau:

    stages:
      - Lint
    lint-source:
      stage: Lint
      only:
          - cicd
      script:
        - swiftlint

    Kết quả khi commit code lên branch cicd, chúng ta sẽ có kết quả log như sau:

    Mặc định Swiftlint job sẽ trả về kết quả thành công exit 0


    Ở đây chúng ta thấy, hệ thống đã phát hiện được 16 lỗi ở 3 files code. Tuy nhiên, job vẫn success và các merge request vẫn được phép tiếp tục vì Swiftlint vẫn trả về success thay vì error. Đây là một rủi ro, và để ép chặt các thành viên phải fix hết các lỗi Swiftlint trước khi được merge code, ta sẽ thêm tham số vào script như sau:

    script:
      - swiftlint --strict

    Kết quả thu được, hệ thống scan code và trả về lỗi nếu swiftlint chưa được fix hết.

    Swiftlint job sẽ trả về lỗi exit 1 khi sử dụng tham số –strict


    Trong trường hợp chúng ta muốn “dễ dãi”, cho phép merge code trong trường hợp các lỗi swiftlint vẫn còn hoặc muốn job trả về thành công để tiếp tục các job tiếp theo, ta sẽ cấu hình thêm một chút như sau:

    stages:
      - Lint
    lint-source:
      stage: Lint
      allow_failure: true
      only:
        - cicd
      script:
        - swiftlint --strict
    Các job fail khi cấu hình allow_failure = true


    Khi ấy, các job phía sau vẫn sẽ được thực hiện, merge request vẫn sẽ được approve nhưng với thông báo Warning đáng chú ý hơn.

    Như vậy là đã hoàn thành công đoạn triển khai CI với Swiftlint, ở bài viết tiếp theo, mình sẽ hướng dẫn các bạn triển khai CI với SonarQube, Blackduck

    Authors

    LinhNB1

  • Tạo HTTP Request với URLSession

    Tạo HTTP Request với URLSession

    Alamofire là thư viện về HTTP Networking được biết đến nhiều nhất trong lập trình iOS sử dụng Swift. Vậy nếu không sử dụng Alamofire thì chúng ta thực hiện các HTTP request như thế nào? Dưới đây là một trong những cách để thực hiện các request với URL Loading System được cung cấp ở ngay thư viện cơ bản nhất Foundation của Apple.

    URL Loading System bao gồm các cấu trúc, giao thức để làm việc với URL và giao tiếp với server. Ở bài viết này chúng ta sẽ làm việc chủ yếu với lớp URLSession.

    1. Thực hiện Request đơn giản với URLSession

    Lấy ví dụ thực hiện search key word “Son Tung MTP” với iTunes Store API, chúng ta cần thực hiện một request với thông tin sau đây:

     let iTunesHostURL = "https://itunes.apple.com/search?term=Son+Tung+MTP"
     guard let url = URL(string: iTunesHostURL) else {
         print("URL Not valid")
         return
     } 

    Để thực thi request trên, ta sử dụng URLSession như sau:

     let task1 = session.dataTask(with: url, completionHandler: { data, response, error in
         if let error = error {
             print(error)
         }
         if let response = response {
             print(response)
         }
         if let data = data, let dataString = String(data: data, encoding: .utf8) {
             print(dataString)
         }
     })
     task1.resume() 

    Nhân vật chính là phương thức dataTask(with:completionHandler:) của URLSession, ở đây ta chỉ cần khởi tạo một URL với đường dẫn sẵn có, sau đó xử lý dữ liệu bên trong completionHandler block của phương thức trên. Với TH trên, thông tin in ra của response (HTTP status code, headers) hiển thị trên output log như sau:

    { Status Code: 200, Headers { "Cache-Control" = ( "max-age=86400" ); "Content-Disposition" = ( "attachment; filename=1.txt" ); "Content-Encoding" = ( gzip ); "Content-Length" = ( 7849 ); "Content-Type" = ( "text/javascript; charset=utf-8" ); Date = ( "Tue, 08 Dec 2020 08:55:43 GMT" ); "Strict-Transport-Security" = ( "max-age=31536000" ); Vary = ( "Accept-Encoding" ); "apple-originating-system" = ( MZStoreServices ); "apple-seq" = ( 0 ); "apple-timing-app" = ( "277 ms" ); "apple-tk" = ( false ); b3 = ( "ab8b7390acfee63d8fddd6db794d7776-27de63d447f98e57" ); "x-apple-application-instance" = ( 2007320 ); "x-apple-application-site" = ( ST11 ); "x-apple-jingle-correlation-key" = ( VOFXHEFM73TD3D6523NXSTLXOY ); "x-apple-orig-url" = ( "https://itunes.apple.com/search?term=Son+Tung+MTP" ); "x-apple-partner" = ( "origin.0" ); "x-apple-request-uuid" = ( "ab8b7390-acfe-e63d-8fdd-d6db794d7776" ); "x-apple-translated-wo-url" = ( "/WebObjects/MZStoreServices.woa/ws/wsSearch?term=Son+Tung+MTP&urlDesc=" ); "x-b3-spanid" = ( 27de63d447f98e57 ); "x-b3-traceid" = ( ab8b7390acfee63d8fddd6db794d7776 ); "x-cache" = ( "TCP_MISS from a113-171-230-176.deploy.akamaitechnologies.com (AkamaiGHost/10.2.2.1-31386017) (-)" ); "x-cache-remote" = ( "TCP_MISS from a23-67-57-164.deploy.akamaitechnologies.com (AkamaiGHost/10.2.2.1-31386017) (-)" ); "x-content-type-options" = ( nosniff ); "x-true-cache-key" = ( "/L/itunes.apple.com/search vcd=2897 ci2=term=Son+Tung+MTP///" ); "x-webobjects-loadaverage" = ( 0 ); } }

    Thông tin data trả về sau khi được convert thành String hiển thị lên output log như sau:

    { "resultCount":48, "results": [ {"wrapperType":"track", "kind":"song", "artistId":705007874, "collectionId":1380326325, "trackId":1380326334, "artistName":"Sơn Tùng M-TP", "collectionName":"Lạc Trôi - Single", "trackName":"Lạc Trôi", "collectionCensoredName":"Lạc Trôi - Single", "trackCensoredName":"Lạc Trôi", "artistViewUrl":"https://music.apple.com/us/artist/s%C6%A1n-t%C3%B9ng-m-tp/705007874?uo=4", "collectionViewUrl":"https://music.apple.com/us/album/l%E1%BA%A1c-tr%C3%B4i/1380326325?i=1380326334&uo=4", "trackViewUrl":"https://music.apple.com/us/album/l%E1%BA%A1c-tr%C3%B4i/1380326325?i=1380326334&uo=4", "previewUrl":"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview118/v4/d3/ee/c7/d3eec748-a929-3ed5-4850-f79a13c34dbb/mzaf_4185904769253643682.plus.aac.p.m4a", "artworkUrl30":"https://is5-ssl.mzstatic.com/image/thumb/Music118/v4/02/81/33/028133f4-db9c-2665-bfd2-7a38389355fc/source/30x30bb.jpg", "artworkUrl60":"https://is5-ssl.mzstatic.com/image/thumb/Music118/v4/02/81/33/028133f4-db9c-2665-bfd2-7a38389355fc/source/60x60bb.jpg", "artworkUrl100":"https://is5-ssl.mzstatic.com/image/thumb/Music118/v4/02/81/33/028133f4-db9c-2665-bfd2-7a38389355fc/source/100x100bb.jpg", "collectionPrice":1.29, "trackPrice":1.29, "releaseDate":"2016-12-31T12:00:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"notExplicit", "discCount":1, "discNumber":1, "trackCount":1, "trackNumber":1, "trackTimeMillis":232889, "country":"USA", "currency":"USD", "primaryGenreName":"Alternative", "isStreamable":true}...]}

    2. Thực hiện Request tuỳ chỉnh HTTP Request Methods

    Có tổng cộng 9 loại HTTP Request Methods, hai loại phổ biến nhất là GET và POST. Đối với các API được định nghĩa Method, chúng ta sẽ sử dụng thêm URLRequest để tuỳ chỉnh các methods trên. Tiếp tục với ví dụ ở trên, ta sẽ định nghĩa HTTP method là GET như sau:

     var request = URLRequest(url: url)
     request.httpMethod = "GET"

    Đối với các HTTP Methods khác, ta chỉ cần thay đổi giá trị httpMethod dưới dạng string như “POST”, “PUT”, …
    Để gửi request với URLRequest, ta sử dụng phương thức dataTask(with:completionHandler:), kết quả vẫn không đổi so với ví dụ request ở trên:

     let task = session.dataTask(with: request, completionHandler: { data, response, error in
         if let error = error {
             print(error)
         }
         if let response = response {
             print(response)
         }
         if let data = data, let dataString = String(data: data, encoding: .utf8) {
             print(dataString)
         }
     })
     task.resume() 

    3. Thực hiện Request tuỳ chỉnh HTTP Header

    Đối với nhiều hệ thống, việc yêu cầu thêm các thông tin của Client khi gửi request lên là bắt buộc, và sẽ được truyền lên trên HTTP Headers của Request. Ví dụ với một request yêu cầu client gửi lên các thông tin Authorization, Content-Type, Accept-Language, ta sẽ tuỳ chỉnh URLRequest như sau:

     var request = URLRequest(url: url)
     request.httpMethod = "GET"
     request.setValue("Basic bGluaG5iMTpsaW5obmIx", forHTTPHeaderField: "Authorization")
     request.setValue("application/json; charse=UTF-8", forHTTPHeaderField: "Content-Type")
     request.setValue("en-US", forHTTPHeaderField: "Accept-Language") 

    4. Thực hiện Request tuỳ chỉnh HTTP Body

    HTTP Body có rất nhiều kiểu, ví dụ như raw data (string, json), x-www-form-urlencoded, form-data, … Bài viết sẽ tập trung vào một số kiểu body phổ biến:

    4.1 HTTP Body với x-www-form-urlencoded

    x-www-form-urlencoded có dạng key-value và sẽ được mã hoá theo dạng URL Encoding (Percent Encoding) khi gửi request. Để gửi một request với body là x-www-form-urlencoded ta cần cài đặt HTTP Header như sau:

     request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")

    Đối với phần body, lấy ví dụ dữ liệu truyền lên như sau:

     let requestDictionary = ["term":"Son Tung MTP", "country":"VN", "lang":"vi_vn"]

    Chúng ta sẽ sử dụng URLComponent để chứa các thành phần key-value, sau đó encode thành URL

     var requestBodyComponent = URLComponents()
     requestBodyComponent.queryItems = [URLQueryItem(name: "term", value: "Son Tung MTP"),
                                                URLQueryItem(name: "country", value: "VN"),
                                                URLQueryItem(name: "lang", value: "vi_vn")] 
    print(requestBodyComponent.string) // print -> "?term=Son%20Tung%20MTP&country=VN&lang=vi_vn" 

    Sau đó, ta chỉ cần convert URL Encoded String thành Data và truyền vào body của request:

     request.httpBody = requestBodyComponent.query?.data(using: .utf8) 

    4.2 HTTP Body với JSON

    JSON là một kiểu dữ liệu rất phổ biến và được hỗ trợ rất tốt. Do đó, việc sử dụng request với JSON cũng rất dễ dàng. Vẫn với ví dụ dictionary ở trên, chúng ta muốn truyền đi ở dạng Json như sau “{\”country\”:\”VN\”,\”lang\”:\”vi_vn\”,\”term\”:\”Son Tung MTP\”}”:

     request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    let requestDictionary = ["term":"Son Tung MTP", "country":"VN", "lang":"vi_vn"]
     guard let jsonData = try? JSONEncoder().encode(requestDictionary) else {
         print("Error: Failed to JSON data")
         return
     } 
     request.httpBody = jsonData

    Chúng ta có thể sử dụng nhiều kiểu dữ liệu đầu vào để encode thành json Data với phương thức JSONEncoder().encode(_ value: T), chỉ cần kiểu dữ liệu đó có thuộc tính Encodable. Vì vậy, các lớp để chứa dữ liệu cho request body dạng JSon thường được kế thừa Encodable hoặc rộng hơn là Codable.

    4.3 HTTP Body với form data

    Kiểu dữ liệu multipart/form-data thường được sử dụng khi request có chứa các file đính kèm cần upload (Ví dụ: JPG, PNG, …). Lấy ví dụ với trường hợp cần upload một UIImage dạng PNG:

     let uploadImage = UIImage(named: "sample")

    Trong trường hợp này, HTTP Header và Body sẽ có dạng như sau:

    Content-Type:multipart/form-data; boundary=--26142EB6-EDB0-4F36-A4EE-079B11F200C3
    --26142EB6-EDB0-4F36-A4EE-079B11F200C3
    
    Content-Disposition: form-data; name="image_field"; filename="sample.jpg"
    Content-Type: image/jpg
    
    ￘¢\u{10}䩆䥆\u{01}\0£Œ䕸楦\0䵍*\0\u{08}\u{05}Ē\u{03}\0\u{01}\u{01}\0Ě\u{05}\0\u{01}\0Jě\u{05}\0\u{01 // Image Data
    --26142EB6-EDB0-4F36-A4EE-079B11F200C3--

    Đầu tiên, với header sẽ có format như sau:

    Content-Type:multipart/form-data; boundary=\(boundary)

    Giá trị boundary là free defined bởi người dùng và tuân theo một số ràng buộc.

    Đổi với phần body, mỗi một part sẽ có format như sau:

    --\(boundary)  // Start part 1
     Content-Disposition: form-data; name="\(partName)"
    
    \(partData)
    --\(boundary) // Start part 2
     Content-Disposition: form-data; name="\(partName)"
     Content-Type: \(contentType) // application/json
    
    \(partData)
    --\(boundary) // Start part 3
     Content-Disposition: form-data; name="\(partName)"; filename="\(fileNameOnlyForFile)"
     Content-Type: \(contentType) // image/jpg
    
    \(partData)

    Trong đó, trường filename là optional, chỉ sử dụng khi upload file, đối với dữ liệu plain thì không cần thiết. Trường contentType có một số loại như mimeType của image, application/json hoặc không cần thiết đối với plain text. partData chính là value của tham part cần truyền lên dưới dạng Data.

    Ở cuối cùng của HTTP Body, ta sẽ thêm một block nữa để đánh dấu kết thúc của phần body:

    --\(boundary)--

    Sample code sẽ trông như sau:

     let uploadImage = UIImage(named: "sample")
     guard let imageData = uploadImage?.jpegData(compressionQuality: 1) else {
         return
     }
    
     let boundary = "Boundary-\(UUID().uuidString)"
     request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
     
     let httpBody = NSMutableData()
    // Loop following 5 block for each form part
     httpBody.appendString("--\(boundary)\r\n")
     httpBody.appendString("Content-Disposition: form-data; name=\"image_field\"; filename=\"sample.jpg\"\r\n")
     httpBody.appendString("Content-Type: image/jpg\r\n\r\n")
     httpBody.append(imageData)
     httpBody.appendString("\r\n")
    
    // Add the ending block for body
     httpBody.appendString("--\(boundary)--")
     
     request.httpBody = httpBody as Data 
     extension NSMutableData {
       func appendString(_ string: String) {
         if let data = string.data(using: .utf8) {
           self.append(data)
         }
       }
     } 

    5. Thực hiện Request download file

    Để thực hiện download một file, ta có thể sử dụng downloadTask(with:completionHandler:), dưới đây là một ví dụ download file jpg và hiển thị lên UI:

     let iTunesHostURL = "https://phongvu.vn/cong-nghe/wp-content/uploads/2018/07/dota_2_traxe_drow_ranger_art_milimalism_97328_1920x1080.jpg"
     guard let url = URL(string: iTunesHostURL) else {
         print("URL Not valid")
         return
     }
     
     let task = URLSession.shared.downloadTask(with: url) { localURL, urlResponse, error in
         guard let localURL = localURL,
               let data = try? Data.init(contentsOf: localURL),
               let image = UIImage.init(data: data) else {
             print("Failed to read downloaded file to image")
             return
         }
         DispatchQueue.main.async {
             let imageview = UIImageView.init(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
             imageview.image = image
             imageview.contentMode = .scaleToFill
             self.view.addSubview(imageview)
         }
     }
     task.resume() 

    6. Một số chú ý khi sử dụng URLSession

    1. Completion Handler được thực thi ở delegate queue, do đó các cập nhật liên quan đến các thành phần thuộc UIKit (chỉ thực thi được trên main thread) cần đặc biệt chú ý khi sử dụng bên trong completionHandler.
    2. Kể từ iOS 9, mặc định các Request HTTP sẽ bị block theo chính sách AppTransportSecurity của Apple. Do đó nếu request không phải HTTPS mà là HTTP, cần cài đặt lại thông tin như sau trong info.plist
     <key>NSAppTransportSecurity</key>
     <dict>
         <key>NSAllowsArbitraryLoads</key>
         <true/>
     </dict> 

  • Ứng dụng đa ngôn ngữ cho APNs

    Ứng dụng đa ngôn ngữ cho APNs

    Để làm đa ngôn ngữ cho APNs rất đơn giản, đầu tiên chúng ta cần những thứ sau đây:
    1. File Localizable.strings define các localization
    2. Đăng ký Push Notification cho ứng dụng
    Ở đây mình mặc định hai công việc trên đã được hoàn thành, và tập trung vào việc xử lý đa ngôn ngữ cho APNs.

    Thông thường, một Notification đơn giản sẽ có nội dung như sau:

    // Notification payload
    {
        "aps":{
           "alert":{
              "title":"Đây là tiêu đề thông báo",
              "body":"Còn đây chắc là nội dung thông báo"
           }
        }
    }
    Nội dung notification hiển thị trên device

    Như ví dụ trên, giá trị tương ứng với key titlebody sẽ được hiển thị trực tiếp vào tiêu đề và nội dung của notification như ảnh. Để áp dụng đa ngôn ngữ, ta sẽ thay thế titlebody bằng hai key khác là title-loc-keyloc-key, tiếp đó ta truyền giá trị của hai trường trên bằng key của localization đã define trong file Localizable.strings:

    // Notification payload
    {
        "aps":{
           "alert":{
              "title-loc-key":"notification_title",
              "loc-key":"notification_content"
           }
        }
    }
    // Localizable.strings content
     "notification_title" = "Thông báo ";
     "notification_content" = "Nội dung thông báo"; 
    Nội dung notification hiển thị trên device

    Như vậy là ta xong với các Notification có nội dung cố định. Đối với các Notification có sử dụng thêm tham số ví dụ như “Bạn có 10 thông báo mới” hay “NhatHM đã ném tiền vào mặt bạn”, chúng ta sử dụng thêm hai key title-loc-argsloc-args, và setting như sau:

    // Notification payload
    {
        "aps":{
           "alert":{
              "title-loc-key":"notification_title_args",
              "title-loc-args":["10"],
              "loc-key":"notification_content_agrs",
              "loc-args":["tinh thần"]
           }
        }
     }
    // Localizable.strings content
    "notification_title_args" = "Bạn có thêm %@ thông báo mới"; "notification_content_agrs" = "Nội dung thông báo với giá trị %@";
    Nội dung notification hiển thị trên device

    Để đề phòng trường hợp ứng dụng chưa định nghĩa các key trong file Localizable.strings, ta chỉ cần cung cấp thêm hai key titlebody và truyền vào giá trị mặc định cho Notification. Hai giá trị này sẽ được hiển thị khi ứng dụng client không tìm thấy strings được trả về trong Notification payload. Nên để đẹp trai thì Notification sẽ như thế này:

    // Notification payload
    {
        "aps":{
           "alert":{
              "title":"Đây là tiêu đề thông báo",
              "body":"Còn đây chắc là nội dung thông báo",
              "title-loc-key":"notification_title_args",
              "title-loc-args":["10"],
              "loc-key":"notification_content_agrs",
              "loc-args":["tinh thần"]
           }
        }
     }

    Như vậy là đủ để quẩy đa ngôn ngữ cho Notification rồi đấy 😀

  • Triển khai CI/CD với GitLab-CI (P1 – Cài đặt và khởi tạo)

    Triển khai CI/CD với GitLab-CI (P1 – Cài đặt và khởi tạo)

    Chú ý 1: Phạm vi của bài viết bao gồm việc triển khai CI/CD với GitLab-CI trực tiếp trên thiết bị MacOS, không sử dụng container

    Chú ý 2: Do nội dung của phần này hơi nhiều nên mình xin phép tách thành nhiều bài nhỏ để mọi người đọc đỡ bị mệt mỏi và dễ focus vào các phần muốn đọc hơn

    Bỏ qua các triết lý hay định nghĩa về CI/CD, mình xin phép đề cập luôn đến việc triển khai CI/CD . Để triển trai CI/CD với GitLab-CI, chúng ta cần làm các bước sau:

    1. Cài đặt gitlab-runner trên thiết bị node (MacOS, Window)
    2. Khởi tạo runner liên kết với GitLab Repository
    3. Cấu hình GitLab Runner với config.toml (optional)
    4. Cấu hình các jobs CI/CD với .gitlab-ci.yml

    1. Cài đặt gitlab-runner trên thiết bị node

    Để cài đặt gitlab-runner trên máy, chạy command sau trên terminal:

    > brew install gitlab-runner

    Ngoài ra, các bạn có thể tham khảo quá trình cài đặt trên doc của GitLab (https://docs.gitlab.com/runner/install/)

    Sau khi cài xong, bạn có để sử dụng command sau để kiểm tra phiên bản hiện tại trên máy:

    > gitlab-runner verify

    2. Khởi tạo runner liên kết với GitLab Repository

    Chú ý: Để truy cập các chức năng liên quan đến cài đặt GitLab-CI, hãy đảm bảo tài khoản được cấp quyền Maintainer hoặc cao hơn.

    Đầu tiên, chúng ta truy cập GitLab Repo cần chaỵ CI/CD, sau đó chọn Settings -> CI/CD -> Expand section Runners.

    Thông tin để đăng ký Runner sẽ hiển thị ở đây

    Ở đây, hãy chú ý đến hai giá trị là gitlab-ci coordinatorgitlab-ci token

    Sau đó. ta dùng command sau để bắt đầu khởi tạo Runner:

    > gitlab-runner register

    Tiếp đó, ta nhập các thông tin của Runner theo hướng dẫn

    Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
    > https://gitlab.techover.io/ (gitlab-ci coordinator ở trên kia)
    Please enter the gitlab-ci token for this runner:
    > oH5aKzZsqibPr******** (gitlab-ci token ở trên kia)
    Please enter the gitlab-ci description for this runner:
    > Techover IO Runner (Mô tả cho runner)
    Please enter the gitlab-ci tags for this runner (comma separated):
    > develop (gắn tag cho runner)
    Please enter the executor: ssh, virtualbox, docker+machine, kubernetes, parallels, shell, docker-ssh, docker-ssh+machine, custom, docker:
    > shell (tuỳ chọn executor, ở đây mình sẽ dùng shell)
     Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!  

    Như vậy là đã đăng ký xong runner với GitLab Repo, giờ chúng ta sẽ start runner bằng command sau:

    > gitlab-runner start 

    Reload lại page GitLab, ta sẽ thấy Runner xuất hiện trong danh sách Runners của Repo. Như vậy là đã hoàn thành việc khởi tạo runner cho Repo.

    3. Cấu hình GitLab Runner với config.toml (Optional)

    Bạn có thể skip qua phần 3 nếu không hứng thú, vì nếu không làm thì GitLab-Ci vẫn chạy bình thường, mà ở đây mình cũng chỉ giới thiệu qua thôi 😀 chi tiết mình sẽ cố gắng làm trong một phần khác trong tương lai . File config.toml bao gồm các cài đặt, tuỳ chỉnh cho runner chạy trên thiết bị. Ta có thể truy cập file config.toml với đường dẫn sau:

    > ~/.gitlab-runner/config.toml 

    Tại đây bạn có thể tuỳ chỉnh rất nhiều các Các chi tiết về việc cài đặt có thể tham khảo trên doc của GitLab: https://docs.gitlab.com/runner/configuration/advanced-configuration.html

    Ví dụ ở đây mình thêm cài đặt giới hạn size của file log lên 40MB (Mặc định là 4MB)

    4. Cấu hình các jobs CI/CD với .gitlab-ci.yml

    GitLab-CI không có cấu hình mặc định cho CI/CD, để cấu hình ta cần thêm file .gitlab-ci.yml vào thư mục gốc của Repo. File sử dụng YAML, có thể tham khảo cú pháp ở link sau: https://docs.gitlab.com/ce/ci/yaml/

    Dưới đây là ví dụ template file .gitlab-ci.yml của GitLab cho Repository sử dụng Swift

    # This file is a template, and might need editing before it works on your project.
    # Lifted from: https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/
    # This file assumes an own GitLab CI runner, setup on a macOS system.
     stages:
       - build
       - test
       - archive
       - deploy
     
    
     build_project:
       stage: build
       script:
         - xcodebuild clean -project ProjectName.xcodeproj -scheme SchemeName | xcpretty
         - xcodebuild test -project ProjectName.xcodeproj -scheme SchemeName -destination 'platform=iOS Simulator,name=iPhone 8,OS=11.3' | xcpretty -s
       tags:
         - ios_11-3
         - xcode_9-3
         - macos_10-13
     
    
     archive_project:
       stage: archive
       script:
         - xcodebuild clean archive -archivePath build/ProjectName -scheme SchemeName
         - xcodebuild -exportArchive -exportFormat ipa -archivePath "build/ProjectName.xcarchive" -exportPath "build/ProjectName.ipa" -exportProvisioningProfile "ProvisioningProfileName"
       only:
         - master
       artifacts:
         paths:
           - build/ProjectName.ipa
       tags:
         - ios_11-3
         - xcode_9-3
         - macos_10-13 

    Để apply các cài đặt trong file .gitlab-ci.yml, ta chỉ cần đẩy file lên origin. Sau đó Runner sẽ tự động nhận và chạy jobs theo những gì đã định nghĩa trong file.

    Để kiểm tra lịch sử chạy các Pipeline, chúng ta truy cập GitLab Repo chaỵ CI/CD, sau đó chọn CI/CD .

    Vậy là chúng ta đã hoàn tất quá trình cài đặt và khởi tạo Gitlab runner cho một Repository. Ở các bài viết tiếp theo, mình sẽ nói về việc cài cắm pipeline để chạy CI/CD cho các dự án thực tế. Xin cảm ơn các bạn đã theo dõi 😀