Git submodule 使用筆記

之前大概知道git 很早以前就有這個功能了,但直到最近在公司上才開始比較有在使用它,也趁這個機會好好的做個筆記,這樣之後就可以直接參考這個筆記就好了。

簡介

Submodule 主要的用途是用來把專案中,會需要共用的程式碼獨立出去成單獨的一個git repo,然後,目前的repo就只是使用其submodule的某一個版本(commit) 而已。

目前專案上碰到的一個需求,就是有多個服務可能會同時需要連接同一個資料庫(SOA 架構);在這樣的情況下,如果我們需要修改DB Migration Schema時,就很有可能需要在每個repo中增加對應的SQL檔案了,這樣其實蠻容易出錯的,也違反了DRY原則。

Git submodule 就很適合用來解決這個問題,我們可以直接把這些DB Migration SQL獨立出來成一個git repo,而任何有需要用到這個資料庫的服務,都可以把這個DB Migration SQL Repo 新增至目前服務下的submodule,這樣,我們只需要讓每個服務Migration SQL Repo 都一直在最新的版號(commit)就好了。

常用的Git submodule 指令

在這邊順便記錄一下,目前常常會用到的一些submodule 指令。

新增submodule 至目前的repo

下面這個指令,可以把github上的repo git@github.com:github-account/github-project.git新增到目前git repo下成為一個submodule。

git submodule add git@github.com:github-account/github-project.git the-submodule-name-in-the-repo

在用上面的指令新增完submodule以後,我們目前的git repo 下就會多了2個unstashed的檔案: .gitmodulesthe-submodule-name-in-the-repo,為了完成新增這個submodule,我們還必須git addgit commit 這2個檔案。

Clone 一個有submodule的git repo

當如果要clone 一個有submodule的repo時,會需要在clone時帶--recurse-submodules 的flag,不然那些submodule 就不會順便被clone 下來,而會呈現是空資料夾。

git clone git@github.com:github-account/git-parent-repo.git --recurse-submodules

Clone 目前repo裡面的submodule

假設我們在最初clone repo時,就沒有帶 --recurse-submodule時,這樣我們如果要在另外clone 那些submodule時,就會需要用下面的指令去做

git submodule update --init

更新目前repo下所有submodule 的版本

當目前repo下的submodule被更新時,我們會需要透過下面的指令去把所有submodules 都更新,在更新完了以後,我們還會需要在做git addgit commit來綁定目前repo所對應的submodule 版本。

git submodule update --recursive --remote

更新特定submodule的版本

若只是要更新某一個submodule的版本的話,我們可以透過下面的指令來更新

cd submodule-folder/
git checkout branch-name
git pull

移除特定的submodule

移除submodule 比較麻煩點,需要依序執行下面的幾個步驟:

從目前的git version,新增一個移除submodule 的變動

git rm --cached /submodule_folder
rm -rf /submodule_folder

從.gitmodules中移除剛剛已移除的submodule

vim .gitmodules

# then remove the related contents

最後要再移除.git/config中的那個submodule的相關資訊

vim .git/config

# then remove the related contents

Reference

使用AWS Localstack S3 筆記

對於常開發與使用AWS的開發者來說,要實作一些整合測試時通常會比較麻煩點,尤其是通常會需要在有網路的環境下做開發與測試。

Github上的這個Localstack 專案,就是為了解決這個痛點而存在的,它幫助AWS開發者搭建本地端的AWS 服務,舉例來說,我們可以透過Localstack在本地端搭建一個S3讓我們所開發的應用程式去連接。

目前Localstack支援的AWS 服務可以在他們的github頁面上找到,常見的API Gateway, DynamoDB, Lambda and S3都可以使用,如果是使用他們的付費方案, 則有支援更多的AWS 服務。

搭建Localstack S3

目前剛好有用到S3,所以記錄一下透過下面docker-compose.yaml來啟用Localstack的方式:

version: "3.3"
 services:
   localstack:
     image: localstack/localstack
     network_mode: bridge
     ports:
       - "4566:4566"
       - "4571:4571"
     environment:
       - SERVICES=s3
       - EDGE_PORT=4566
       - AWS_DEFAULT_REGION=us-east-1
     volumes:
       - "${TMPDIR:-/tmp/localstack}:/tmp/localstack"
       - "/var/run/docker.sock:/var/run/docker.sock"

NOTE:

  • 這邊的SERVICES是可以指定多個服務的,例如SERVICE=s3,lambda
  • EDGE_PORT目前預設也是4566,主要是用來在本地端服務的port number。
  • 更多詳細的docker-compose.yaml 範例可以參考官方的這個例子

使用AWS CLI 連接 Localstack S3

在透過上面提到的方式搭建好本地端的S3以後,其實就可以直接用AWS CLI來去連到我們剛架好的S3了;唯一要注意的是,我們在使用cli 時必須指定對應的endpoint-url到我們剛架好的本地端url。

透過docker aws cli 連接Localstack s3

docker exec -t -e AWS_ACCESS_KEY_ID=1 -e AWS_SECRET_ACCESS_KEY=abc container_localstack_1 aws --endpoint-url=http://127.0.0.1:4566 s3 mb s3://my_bucket

NOTE:
透過AWS Cli連接 Localstack時,必須指定key_idkey,要不然會報錯

透過AWS Go SDK

如果要透過Go AWS SDK 去連接Localstack S3的話,會有個額外要設定的AWS.Config欄位,其具體的範例程式碼為:

awsCfg := &aws.Config{
   Credentials: credentials.NewStaticCredentials(awsKeyId, awsKeySecret, ""),
   Region:      aws.String(region),
}

if customEndpoint != "" {
   awsCfg.Endpoint = aws.String("http://localhost:4566")
   awsCfg.S3ForcePathStyle = aws.Bool(true)
}

在這邊,設定 S3ForcePathStyle=true 主要是為了讓Go SDK與連接S3的時候會把bucket 的資訊透過path的方式去代入到resource的uri中。

舉例來說,如果我們有個bucket=my_bucket,而我們在這個bucket裡面的一個測試檔案test.txt的完整uri為是http://localhost:4566/my_bucket/test.txt;有了這樣的設定,才會讓我們使用Go AWS SDK時,可以正確的存取Localstack S3上的檔案。

如果我們使用S3ForcePathStyle=false的話,則同個檔案, Go AWS SDK會使用這個uri: http://my_bucket.localhost:4566/test.txt 去存取Localstack上的檔案;顯然地,這個uri是有問題的,因為Localstack S3 是在listen http://localhost:4566,而不是http://my_bucket.localhost:4566這個子網域。

更多詳細的說明可以參考官方的這個issue:https://github.com/aws/aws-sdk-go/issues/2743

Reference:

Python Poetry在 VSCode上的設定

筆記一下在VS Code上設定Python的開發環境設定;會寫這篇主要是因為一開始在使用VS Code 時,開啟某個Poetry 管理的Python 專案時,總是無法在編輯器上正確定的讀取到相依的套件。

但使用poetry run python xxx.py時卻又可以正確的執行其專案主程式!

Root Cause

最後,找到問題點主要是在VS Code Python interpreter的設定,也就是說如果我們是使用預設的python 路徑時,VS Code沒辦法正確的找到目前專案的相依套件其安裝的路徑 !

Solution

在知道是Python interpreter的問題後,解決方式就是去設定目前 Python 專案的Python interpreter 為其Poetry所建立的Python virtual env 路徑,主要的設定方式如下:

  1. cmd + shift+p -> Python: Select Interpreter
  2. 指定對應的Poetry Python binary path:

假設目前的poetry專案名稱為 pytest,透過poetry install 後,我的電腦上就會出現類似/Users/xxx/Library/Caches/pypoetry/virtualenvs/pytest-yI9RJA6z-py3.9的資料夾,而我們要指定的interpreter就會是:
/Users/gechen/Library/Caches/pypoetry/virtualenvs/pytest-yI9RJA6z-py3.9/bin/python3.

Reference

透過Restful API傳送檔案

通常如果我們有傳送檔案的需求時,在http的世界裡,我們可以透過restful api定義的兩種content-type來實作:

  • application/x-www-form-urlencoded
  • multipart/form-data

雖然說,之前在工作中也有過一些使用上的經驗,但趁這次工作上又需要碰到,所以來做點筆記。

application/x-www-form-urlencoded

The application/x-www-form-urlencoded content type describes form data that is sent in a single block in the HTTP message body. Unlike the query part of the URL in a GET request, the length of the data is unrestricted.

這裡可以看到,當如果我們需要透過http 傳送單一檔案時,我們可以在body中放入我們需要傳送的檔案;而使用這種方式傳送檔案時,其內容必須遵循以下的encoding rules:

* Control names and values are escaped. Space characters are replaced by `+’, and then reserved characters are escaped as described in [RFC1738], section 2.2: Non-alphanumeric characters are replaced by `%HH’, a percent sign and two hexadecimal digits representing the ASCII code of the character. Line breaks are represented as “CR LF” pairs (i.e., `%0D%0A’).

* The control names/values are listed in the order they appear in the document. The name is separated from the value by `=’ and name/value pairs are separated from each other by `&’.

stackoverflow的這篇文章來看,一般使用這種方式傳送檔案時,平均會增加至少33%的額外流量,所以並不是一個很有效率的方法。

multipart/form-data

In the multipart/form-data content type, the HTTP message body is divided into parts, each containing a discrete section of data.

Each message part requires a header containing information about the data in the part. Each part can contain a different content type; for example, text/plainimage/pngimage/gif, or multipart/mixed. If a parameter specifies multiple files, you must specify the multipart/mixed content type in the part header.

這種類型的content-type,定義了client可以在message body中傳送多個不同的檔案,此外,我們必須為每種檔案設定其檔案類型。

在encoding的部分,這裡被沒有強制使用base64,而是讓客戶端可以指定conding的方式。

根據w3c的文件中提到,multipart 是較適合用來傳送binary file或一些non-ASCII的檔案。

application/octet-stream

通常這種類型的content-type是用來給瀏覽器下載檔案時使用,瀏覽器收到這類型的content-type時,預設就會直接下載到本地端。

但在stackoverflow的這篇文章中有提到,我們其實可以直接透過POST 直接設定content-type 為application/octet-stream,並且在加上以下的header 設定就可以直接指定要傳送的檔案。

Connection: close
Content-Type: application/octet-stream
Content-Length: <content-size>

結論

  • multipart/form-data 已發展多年了,且從文件上來看,在最初定義時就是為了傳送二進制的檔案類型,所以我們若有這方面的需求時,應該盡量使用這個content-type。
  • 如果我們的需求只是傳送單一的小檔案,且又可以使用base64 encoding時,使用application/x-www-form-urlencoded也不失為另一個可考慮的選項。

Reference

Python command line

最近為了想上手Python,所以想說用Python寫個簡易的cli 工具當作目標來當練習;這次就來記錄怎麼一步一步的寫出這個cli程式。
程式的需求,是希望可以設定一個定期備份wordpress 內容的服務,並且在備份完了以後,可以上傳到指定的AWS s3.

Requirements

  • 提供flag 可以讓使用者指定要備份的資料夾
  • 提供flag 可以讓使用者指定要上傳的s3 路徑
  • 備份的資料夾必須先經過壓縮後,才會上傳到s3

Dependencies

  • Python 3+
  • Poetry
  • Pip

Poetry 是目前主流的Python套件管理工具,而這次這個repo主要就是使用poetry來管理其相依的library。

Implementation

Cli flags

Config flags 的部分,主要是透過Click這個套件來實作。這個套件在使用上相當容易上手,透過decorator的方式就可以指定程式在執行時,需要帶入哪些指令。

像是下面這段程式碼,就指定了程式在執行時,需要帶入-s--s3_path 作為s3 的路徑;即使我現在還不是很熟decorator,也可以輕易的讓程式支援flag。

@click.command()
@click.option(
     "-s",
     "--s3_path",
     default="",
     envvar="S3_PATH",
     help="The s3 path for uploading. (e.g. s3://path/to/upload )",
     required=True
 )

Entrypoint

除了讓程式支援flag,我們這邊也需要設定程式在執行時的進入點,通常會是一個自定義的function,其參數會是我們先前所設定的flags。

以下面的程式碼為例,我們指定了cli() 作為我們的這個程式的進入點,並且程式必須有2個參數,分別是inputs3_path。(參數的名稱必須與@click.option中的一致)

def cli(input, s3_path):
     if s3_path.find("s3://") != 0:
         logging.error("incorrect s3 path format")
         exit(1)

if name == "main":
    cli()

Config Poetry cli entrypoint

透過先前的設定,我們已經可以透過一般python 的執行方式(python cli.py)來執行我們的程式了,但為了讓我們的cli程式在執行時可以更像一個執行檔,我們可以透過Poetry來包裝我們的程式,這樣就不需要透過pyhton xxx.py的方式執行了!

Poetry entrypoint function

首先,我們必須先變動我們的程式路徑,並且指定一個可以當進入點的function,這邊我們就可以設定一個新的main(),而其function body就是執行我們先前的cli()

def main():
     cli()

接下來,我們必須在project 根資料夾下,新增一個子資料夾,並且把我們的cli.py移至這個子資料夾下,新的路徑結構為下

backup_to_s3_repo/
  poetry.lock
  pyproject.toml
  README.md
  backup_to_s3/
    __init__.py
    cli.py

Poetry entrypoint configuration

在這邊還需要在設定pyproject.toml,主要是為了讓poetry在執行時,可以用”alias”的方式來執行我們的程式,相關的pyproject.toml的內容如下:

下面的設定,可以讓我們執行poetry run backup_to_s3時,實際上去執行backup_to_s3/cli.py中的main()

[tool.poetry.scripts]
 backup_to_s3 = "backup_to_s3.cli:main"

Build and install

在完成前面所提到的設定之後,我們已經可以透過python backup_to_s3/cli.pypoetry run backup_to_s3來執行我們的程式了;但為了讓我們的程式可以更像一般的系統預設的執行程,我們可以透過下面列出的方式來實作。

Build the poetry project

透過poetry build,poetry 會幫我們把整個poetry project與相關的套件封裝成一個tar.gz 檔,並儲存於dist/資料夾下。

在整個封裝的過程中,poetry會幫我們建立對應的.whlsetup.py檔,以便於我們發佈或分享這個tar.gz 檔。

Install poetry build file by pip

在有了上述的tar.gz檔以後,我們就可以透過pip install的方式,將整個backup_to_s3程式安裝成像系統內建的指令程式了,詳細的指令可以參考下面

pip install dist/backup_to_s3-0.1.0.tar.gz

最後,完整的source code 可以參考這裡:
https://github.com/bamoo456/backup-to-s3

Reference

EBS 定價

最近主管告知了上個月的AWS帳單,EC2的帳單暴漲到了$2000!
主管提到了,這是他預期內的金額,不過提醒我要記得關閉最近測試開的那些機器與資源。當然,這金額對我來說是個非常意外的數字,之前的測試都是測試完以後我就馬上關掉機器了,每次測試時間最長也不超過3小時!

後來主管一路追查下去,原來我之前開了一堆io2的EBS,在測試完了以後也沒有刪除,而這些EBS應該就是造成帳單暴漲的原因!!!

這次的失誤,讓我知道以後要記得去看一下EBS是否有忘了刪掉的disk;不過也趁這機會好好研究一下EBS的計價方式:

EBS Volume Charges

AWS的這篇文章中提到的例子,可以看出只要EBS被provision以後,其provision的容量(GB)與IOPS都會被納入計價:

舉個例子,如果我在一台EC2上掛了一個100GB, 10000 IOPS的io2 EBS,則即使我只使用EBS上其中1GB做資料儲存,我一個月需要在EBS上付的金額為:

10000 IOPS * 0.065 USD + 100GB * 0.125 USD = 662.5 $USD

真的認真算下來,才知道io2的EBS計價那麼貴,這次真的是花錢好好的上了一課0rz

Reference

WordPress 搬家

忙了一個下午,終於把Wordpress從原本的RespberryPi上搬到目前的Ubuntu開發主機上! 而會花那麼多時間,主要是因為在開發機上,我想要用docker-compose的方式去佈署這個Wordpress,而不是直接安裝Wordpress於主機上。

這邊簡單列一下整個流程與相關的細節

佈署Wrodpress docker-compose

主要可以參考wrodpress 官方提供的docker-compose,就可以在有docker環境下的機器順利執行整套wordpress。

以下是我更改後的wordpress docker-compose.yaml

version: "3.3"
 services:
   db:
     image: mariadb:10.5
     ports:
       - "3306:3306"
     volumes:
       - "./my-docker-volumes/db:/var/lib/mysql"
     environment:
       MYSQL_ROOT_PASSWORD: mariadb-root-password
       MYSQL_DATABASE: wordpress-user
       MYSQL_USER: wordpress-user
       MYSQL_PASSWORD: wordpress-password
     restart: always
  wordpress:
     depends_on:
       - db
     image: wordpress:5.6
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress-user
       WORDPRESS_DB_PASSWORD: wordpress-password
     volumes:
       - "./my-docker-volumes/wordpress:/var/www/html"

這邊所使用的MariaDB與Wordpress版本都是以當初在RespberryPi上的版本為主,主要是希望從RespberryPi回復資料時,不會造成一些資料上的問題。

設定本機的Nginx

這邊一開始設定時,也遇到了些css上的問題,後來參考了這連結以後也順利的解決了!
(原本以為是wp-config.php內部有檔參數要改,所以也花了不少時間在這上面)

設定完成的nginx 設定檔如下:

server {
     listen 80;
     server_name blog.gechen.xyz;
     rewrite ^(.*) https://$host$1 permanent;
 }
 server {
    listen 443 ssl;
    server_name blog.gechen.xyz;
    index index.php;
    ssl_certificate /cert_path/fullchain.pem;
    ssl_certificate_key /cert_path/privkey.pem;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1.1 TLSv1.2;
    client_max_body_size 100M;
    location / {
        include snippets/wp-reverse-proxy.conf;
        proxy_pass http://localhost:8000/;
    }
    location /login {
        try_files $uri $uri/ /wp-login.php; 
    }
 }

wp-reverse-proxy.conf的設定檔如下:

add_header X-Frame-Options SAMEORIGIN;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_hide_header X-Frame-Options;

設定Let’s encrypt certificate

之前的certbot也是放在RespberryPi上,所以也趁這個機會搬到目前的開發主機上,所以直接參考了之前的設定方式,在目前的主機上完成了wildcard certificate的申請。

備份與還原Wordpress database

這個部分,我是直接透過mysqpdump的方式,直接把先前整個wordpress db用下面的指令dump:

mysqldump -u wordpress-user -p --all-databases > backup.sql;

再直接還原到新的wordpress db 上:

mysql -h new-wordpress-ip-address -u wordpress-user -p wordpress-db < backup.sql

備份與還原Wordpress files

這部分目前還是有些問題,我主要是參考這篇文章提到的備份整個wordpress files,但在把備份資料夾從Respberry Pi 搬到開發主機並直接用docker-compose mount到內部資料夾後,佈景主題與外掛都有出了點問題!

看來這部分還需要花點時間了解,要如何完整的備份一個Wordpress 網站 !!!

References

Vim 多文件開發

今天看到同事從容用vim 看文件內容時,又看到一些值得記錄的指令。

視窗相關指令

vim -o file1 file2 用水平分割的方式開啟 file1 與 file2

vim -O file1 file2 用垂直分割的方式開啟file1與 file2

在已經開啟檔案的情況下,

:vs : 可以將目前的開啟的檔案, 再同步開啟在另一個垂直分割的視窗。

:sp file1.txt : 可以再另一個水平分割視窗上開啟file1.txt.

:vs file1.txt: 可以再另一個垂直分割視窗上開啟file1.txt.

ctrl + ww : 在多個開啟的視窗中切換

Tabs 相關指令

vim -p file1 fil2 : 用tabs分割的方式開啟file1與 file2

vimi -p *.md : 用wildcard 的方式開啟所有.md 結尾的檔案。

gt : 移動到下一個tab

gT: 移動到上一個tab

number + gt : 移到動第ith個tab

:tabedit file : 在另一個tab 上開啟file

Vim configuration

github 上找到了這個別人設定好的vimrc,直接鍵入下面的指令就可以開箱即用了

git clone --depth=1 https://github.com/amix/vimrc.git ~/.vim_runtime 
sh ~/.vim_runtime/install_awesome_vimrc.sh

為開啟的檔案中,顯示row number。

vim ~/.vimrc 開啟設定檔以後,加入下面的指令以後,之後開啟任何檔案時顯示row number.

set number

Reference

Vim 常用指令

最近開始較常使用vim,感覺可以趁這個機會把一些常用的指令整理一下XD

編輯相關指令

i : 在目前編輯的指標處,直接進入編輯模式。

a : 移動到目前編輯指標處的下一個字,並直接進入編輯模式。

s : 在目前編輯的指標處,刪除目前的當下字,並進入編輯模式。

shift + i : 在目前的這一列上,將指標移到最前方,並進入編輯模式。

shift + a : 在目前的這一列上,將指標移到最後方,並進入編輯模式。

o : 在目前的這一列上插入下一列,將指標移到下一列的最前方,並進入編輯模式。

shift + o: 在目前的這一列上插入上一列,將指標移到上一列的最前方,並進入編輯模式。

移動相關指令

j : 往上移動一個單位

k: 往下移動一個單位

backspaceh: 往左移動一個單位

spacel: 往右移動一個單位

數字+{j 或 k 或 h 或 l} : 往特定方向移動所指定的單位數。 (e.g. 20j -> 往上移動20列)

w: 往右移動到下一個字開頭

b: 往左移動到這個字開頭,或移動到上一個字的開頭

e: 往右移動到目前這個字的結尾

^: 移動到目前這一列的開頭

$: 移動到目前這一列的結尾

刪除相關指令(cut)

x: 刪除目前指標所在的這個字元。

D: 刪前目前指標所在的字元到整列結束的所有字。

dd : 移除目前指標所在的這一列,並複製下來。

dw : 刪除目前指標處到這個字尾的整個字。

數字 + d + {j 或k或h或l} : 往特定方向刪除指定的單位數。

(e.g. 20dl -> 往右刪除20個單位;20dd往下刪除了20列)

搜尋相關指令

/ : 進入搜尋模式

進入搜尋模式後,就可以打入要搜尋的字,並鍵入enter開始搜尋文章中出現的字。

在開始搜尋後,可以使用n ,搜尋下一個出現的字;而用shift + n則可以搜尋上一個出現的字。

文字取代相關指令

:%s/${search_word}/${replace_word}/${sub_command}: 為最基本的取代指令,如果${sub_command}=c 則代表每次的取代發生時,都會詢問使用者。

:0,30s/${search_word}/${replace_word/${sub_command} :為只取代0-30列中間出現的${search_word}

參考連結

文字選取相關指令

v : 進入文字選取模式。

在進入了文字選取模式以後,就可以透過{j 或 k 或 h 或l }來進行文字選擇,在選擇到所想要的字節以後,就可以透過 y 來複製(copy)所選的這段文字;或著用d 來剪下(cut)所選擇的文字。

https://vim.fandom.com/wiki/Copy,_cut_and_paste

相關的查詢連結可以參考以下: