From d08d9a51826c6e5b81b2abc4a7bdb9814457b7c5 Mon Sep 17 00:00:00 2001 From: dylankyc <@.@> Date: Fri, 13 Dec 2024 12:59:05 +0800 Subject: [PATCH] init --- .github/workflows/mdbook.yml | 54 + .gitignore | 1 + .img/import-orders-from-schema.png | Bin 0 -> 199261 bytes .img/import-orders.png | Bin 0 -> 269370 bytes book.toml | 6 + run.sh | 5 + src/SUMMARY.md | 59 + .../runner/install-gitlab-runner-on-ubuntu.md | 222 ++ ...egister-gitlab-runner-on-amazon-linux-2.md | 162 ++ .../cronjob/cronjob-to-restart-deployment.md | 325 +++ src/mui/upgrade/upgrade-from-v4-to-v5.md | 81 + .../render-all-chartjs-charts.md | 448 ++++ .../actix/prometheus-support-to-actix-web.md | 163 ++ ...started-function-when-using-actix-crate.md | 879 ++++++++ src/rust/diesel/upgrade-diesel-to-2.0.md | 346 +++ src/rust/diesel/use-jsonb-in-diesel.md | 1851 +++++++++++++++++ ...lication-error-in-actix-web-application.md | 341 +++ ...turn-error-when-unwrap-option-when-none.md | 161 ++ src/rust/grpc/rust-grpc-helloworld.md | 531 +++++ ...type-using-serde-as-in-serde-with-crate.md | 397 ++++ .../async-healthcheck-multiple-endpoints.md | 144 ++ src/rust/tokio/tokio-codec.md | 1184 +++++++++++ src/solana/README.md | 1 + ...0\345\217\221solana\347\232\204program.md" | 1835 ++++++++++++++++ ...33\345\273\272\350\264\246\346\210\267.md" | 1 + .../gitlab/start-gitlab-using-terraform.md | 890 ++++++++ .../import/review-terraform-import.md | 267 +++ ...rm-import-dms-replication-instance-blog.md | 618 ++++++ .../a-good-developer-knows-management.md | 194 ++ ...t-permission-management-using-terraform.md | 205 ++ theme/book.js | 690 ++++++ theme/css/chrome.css | 640 ++++++ theme/css/general.css | 242 +++ theme/css/print.css | 50 + theme/css/variables.css | 309 +++ theme/favicon.png | Bin 0 -> 5679 bytes theme/favicon.svg | 22 + theme/fonts/OPEN-SANS-LICENSE.txt | 202 ++ theme/fonts/SOURCE-CODE-PRO-LICENSE.txt | 93 + theme/fonts/fonts.css | 100 + .../open-sans-v17-all-charsets-300.woff2 | Bin 0 -> 44352 bytes ...open-sans-v17-all-charsets-300italic.woff2 | Bin 0 -> 40656 bytes .../open-sans-v17-all-charsets-600.woff2 | Bin 0 -> 44936 bytes ...open-sans-v17-all-charsets-600italic.woff2 | Bin 0 -> 42120 bytes .../open-sans-v17-all-charsets-700.woff2 | Bin 0 -> 44988 bytes ...open-sans-v17-all-charsets-700italic.woff2 | Bin 0 -> 40800 bytes .../open-sans-v17-all-charsets-800.woff2 | Bin 0 -> 44536 bytes ...open-sans-v17-all-charsets-800italic.woff2 | Bin 0 -> 40812 bytes .../open-sans-v17-all-charsets-italic.woff2 | Bin 0 -> 41076 bytes .../open-sans-v17-all-charsets-regular.woff2 | Bin 0 -> 43236 bytes ...source-code-pro-v11-all-charsets-500.woff2 | Bin 0 -> 59140 bytes theme/highlight.css | 83 + theme/highlight.js | 54 + theme/index.hbs | 335 +++ 54 files changed, 14191 insertions(+) create mode 100644 .github/workflows/mdbook.yml create mode 100644 .gitignore create mode 100644 .img/import-orders-from-schema.png create mode 100644 .img/import-orders.png create mode 100644 book.toml create mode 100755 run.sh create mode 100644 src/SUMMARY.md create mode 100644 src/gitlab/runner/install-gitlab-runner-on-ubuntu.md create mode 100644 src/gitlab/runner/register-gitlab-runner-on-amazon-linux-2.md create mode 100644 src/kubernetes/cronjob/cronjob-to-restart-deployment.md create mode 100644 src/mui/upgrade/upgrade-from-v4-to-v5.md create mode 100644 src/react/functional-component/render-all-chartjs-charts.md create mode 100644 src/rust/actix/prometheus-support-to-actix-web.md create mode 100644 src/rust/actix/send-http-request-in-handle-function-and-started-function-when-using-actix-crate.md create mode 100644 src/rust/diesel/upgrade-diesel-to-2.0.md create mode 100644 src/rust/diesel/use-jsonb-in-diesel.md create mode 100644 src/rust/error/how-to-organise-application-error-in-actix-web-application.md create mode 100644 src/rust/error/return-error-when-unwrap-option-when-none.md create mode 100644 src/rust/grpc/rust-grpc-helloworld.md create mode 100644 src/rust/serde/serialize-time-offsetdatetime-type-using-serde-as-in-serde-with-crate.md create mode 100644 src/rust/tokio/async-healthcheck-multiple-endpoints.md create mode 100644 src/rust/tokio/tokio-codec.md create mode 100644 src/solana/README.md create mode 100644 "src/solana/quickstart/\344\270\215\344\275\277\347\224\250Anchor\345\274\200\345\217\221solana\347\232\204program.md" create mode 100644 "src/solana/quickstart/\344\275\277\347\224\250TypeScript\345\210\233\345\273\272\350\264\246\346\210\267.md" create mode 100644 src/terraform/gitlab/start-gitlab-using-terraform.md create mode 100644 src/terraform/import/review-terraform-import.md create mode 100644 src/terraform/import/terraform-import-dms-replication-instance-blog.md create mode 100644 src/terraform/management/a-good-developer-knows-management.md create mode 100644 src/terraform/s3/s3-bucket-permission-management-using-terraform.md create mode 100644 theme/book.js create mode 100644 theme/css/chrome.css create mode 100644 theme/css/general.css create mode 100644 theme/css/print.css create mode 100644 theme/css/variables.css create mode 100644 theme/favicon.png create mode 100644 theme/favicon.svg create mode 100644 theme/fonts/OPEN-SANS-LICENSE.txt create mode 100644 theme/fonts/SOURCE-CODE-PRO-LICENSE.txt create mode 100644 theme/fonts/fonts.css create mode 100644 theme/fonts/open-sans-v17-all-charsets-300.woff2 create mode 100644 theme/fonts/open-sans-v17-all-charsets-300italic.woff2 create mode 100644 theme/fonts/open-sans-v17-all-charsets-600.woff2 create mode 100644 theme/fonts/open-sans-v17-all-charsets-600italic.woff2 create mode 100644 theme/fonts/open-sans-v17-all-charsets-700.woff2 create mode 100644 theme/fonts/open-sans-v17-all-charsets-700italic.woff2 create mode 100644 theme/fonts/open-sans-v17-all-charsets-800.woff2 create mode 100644 theme/fonts/open-sans-v17-all-charsets-800italic.woff2 create mode 100644 theme/fonts/open-sans-v17-all-charsets-italic.woff2 create mode 100644 theme/fonts/open-sans-v17-all-charsets-regular.woff2 create mode 100644 theme/fonts/source-code-pro-v11-all-charsets-500.woff2 create mode 100644 theme/highlight.css create mode 100644 theme/highlight.js create mode 100644 theme/index.hbs diff --git a/.github/workflows/mdbook.yml b/.github/workflows/mdbook.yml new file mode 100644 index 0000000..c8baa3f --- /dev/null +++ b/.github/workflows/mdbook.yml @@ -0,0 +1,54 @@ +# Sample workflow for building and deploying a mdBook site to GitHub Pages +# +# To get started with mdBook see: https://rust-lang.github.io/mdBook/index.html +# +name: Deploy mdBook site to Pages +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false +jobs: + # Build job + build: + runs-on: ubuntu-latest + env: + MDBOOK_VERSION: 0.4.43 + steps: + - uses: actions/checkout@v4 + - name: Install mdBook + run: | + curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf -y | sh + rustup update + cargo install --version ${MDBOOK_VERSION} mdbook + - name: Setup Pages + id: pages + uses: actions/configure-pages@v5 + - name: Build with mdBook + run: mdbook build + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./book + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7585238 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +book diff --git a/.img/import-orders-from-schema.png b/.img/import-orders-from-schema.png new file mode 100644 index 0000000000000000000000000000000000000000..31c2510c3a4676c57b79b9f0a216964235f9fc5c GIT binary patch literal 199261 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

u#|EkiM?Y%284Vv`0^)3M%0dC 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 D=Pk6k literal 0 HcmV?d00001 diff --git a/.img/import-orders.png b/.img/import-orders.png new file mode 100644 index 0000000000000000000000000000000000000000..a51b4438e42f737dc2666d91e7d0fa3b47c57785 GIT binary patch literal 269370 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

o8vfy_Q_CM8B3bd!B=` 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%0AyM0jpg~*Mu(RcHpZ6RxoV9~vVDM?&*U38n7Cw{ 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 z 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%0A0X45w#6w*Wo)Yvv(+JB=7U8=jz7|)oI?(rrpdm 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 sin=R> Runner registration is the process that links the runner with one or more GitLab instances. You must register the runner so that it can pick up jobs from the GitLab instance. + +## Gitlab runner token + +You have to obtain gitlab runner token from gitlab to register the runner. + +Here is how to obtain the token from gitlab: + +1. Login to gitlab +2. Click on the "Runners" button +3. Click on the "Tokens" button +4. Click on the "Create token" button +5. Copy the token + +Notice, the gitlab runner authentication tokens have the prefix `glrt-`. + +## Use local volume + +Let's see how to use local system volume mounts to start the Runner container. + +```bash +# Create the directory to mount the docker volume +mkdir -p /srv/gitlab-runner/config + +# Start the GitLab Runner container +docker run -d --name gitlab-runner --restart always \ + -v /srv/gitlab-runner/config:/etc/gitlab-runner \ + -v /var/run/docker.sock:/var/run/docker.sock \ + gitlab/gitlab-runner:latest +``` + +Now the runner is started, let's see how to register it. + +```bash +docker run --rm -it -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner register +``` + +## docker volume + +If you want to use docker volume to start the Runner container, you can use the following command: + +```bash +# Create the Docker volume +docker volume create gitlab-runner-config + +# Start the GitLab Runner container using the volume we just created +docker run -d --name gitlab-runner --restart always \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v gitlab-runner-config:/etc/gitlab-runner \ + gitlab/gitlab-runner:latest +``` + +# Register gitlab runner in non-interactive mode + +You can use non-interactive mode to register the runner, refer to https://docs.gitlab.com/runner/commands/index.html#non-interactive-registration for more details. + +If you want to register the runner on linux, you can use the following command: + +```bash +sudo gitlab-runner register \ + --non-interactive \ + --url "https://gitlab.com/" \ + --token "$RUNNER_TOKEN" \ + --executor "docker" \ + --docker-image alpine:latest \ + --description "docker-runner" +``` + +If you want to register the runner through docker, you can use the following command: + +```bash +docker run --rm -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner register \ + --non-interactive \ + --executor "docker" \ + --docker-image alpine:latest \ + --url "https://gitlab.com/" \ + --token "$RUNNER_TOKEN" \ + --description "docker-runner" +``` + +# stop gitlab-runner + +To stop the gitlab-runner container, you can use the following command: + +```bash +docker stop gitlab-runner && docker rm gitlab-runner +``` + +# Refs + +Install +https://lindevs.com/install-gitlab-runner-on-ubuntu + +Install gitlab runner +https://docs.gitlab.com/runner/install/docker.html + +Another install doc +https://docs.gitlab.com/runner/install/linux-repository.html + +Register runner +https://docs.gitlab.com/runner/register/index.html#docker diff --git a/src/gitlab/runner/register-gitlab-runner-on-amazon-linux-2.md b/src/gitlab/runner/register-gitlab-runner-on-amazon-linux-2.md new file mode 100644 index 0000000..7bbb89d --- /dev/null +++ b/src/gitlab/runner/register-gitlab-runner-on-amazon-linux-2.md @@ -0,0 +1,162 @@ +# Register gitlab runner on Amazon Linux 2 + +- [Intro](#intro) +- [Start AWS EC2 Instance](#start-aws-ec2-instance) +- [Install gitlab runner on Amazon Linux 2](#install-gitlab-runner-on-amazon-linux-2) +- [Register gitlab runner](#register-gitlab-runner) +- [Set GITLAB_PORT to 443](#set-gitlab_port-to-443) +- [Write .gitlab-ci.yml for go project](#write-gitlab-ciyml-for-go-project) +- [Test the runner](#test-the-runner) + +# Intro + +In this article, we will show you how to register gitlab runner on Amazon Linux 2 and setup gitlab ci for go project. + +# Start AWS EC2 Instance + +You can create aws ec2 instance either in aws console or using aws cli. + +Notice you should enable public ip address if your aws ec2 instance is in public subnet of you VPC, otherwise you will not be able to access the internet! + +# Install gitlab runner on Amazon Linux 2 + +Next, we'll install gitlab runner on Amazon Linux 2. Run the following bash script: + +```bash +# install on amazon linux 2 +# refs: https://github.com/beda-software/FAQ/blob/master/aws-ec2-gitlab-runner.md +curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh" | sudo bash +sudo -E yum install gitlab-runner +sudo amazon-linux-extras install docker +sudo service docker start +sudo usermod -a -G docker ec2-user +sudo systemctl enable docker.service +sudo systemctl enable containerd.service +sudo usermod -a -G docker ec2-user +sudo yum install -y git +``` + +# Register gitlab runner + +You can register gitlab runner by using `gitlab-ci-multi-runner` command: + +```bash +# register runner +# sudo gitlab-ci-multi-runner register -n --url GITLAB_URL --registration-token "TOKEN" --executor docker --description "Name of docker runner" --docker-image "docker:latest" --docker-privileged +``` + +Replace `GITLAB_URL` and `TOKEN` with your self-hosted gitlab url and gitlab token. You can go to `https://gitlab.mycompany.com/admin/runners` and click `Register an instance runner` button, and you will see the `TOKEN` in the popup. + +```bash +# register runner in gitlab ccc +sudo gitlab-ci-multi-runner register -n --url https://gitlab.mycompany.com/ --registration-token "A____TOKEN_____A" --executor docker --description "Name of docker runner" --docker-image "docker:latest" --docker-privileged +``` + +Output: + +``` +Runtime platform arch=amd64 os=linux pid=8095 revision=865283c5 version=16.1.0 +Running in system-mode. + +WARNING: Support for registration tokens and runner parameters in the 'register' command has been deprecated in GitLab Runner 15.6 and will be replaced with support for authentication tokens. For more information, see https://gitlab.com/gitlab-org/gitlab/-/issues/380872 +Registering runner... succeeded runner=paaaaaaa +Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! + +Configuration (with the authentication token) was saved in "/etc/gitlab-runner/config.toml" +``` + +You will see a runner with name `paaaaaaa` is registered successfully. + +And you can also config the runner in `/etc/gitlab-runner/config.toml`. + +You can go to runners page in gitlab Admin and check if the runner is registered successfully. + +# Set GITLAB_PORT to 443 + +As the runner is registered successfully, you may encounter an error like this: + +``` +gitlab-runner version 14.10.1 fails to clone a repo configured with a https repo URL, stating HTTP Basic: Access denied. +``` + +As we deploy gitlab using `https://github.com/sameersbn/docker-gitlab`, you may need to set `GITLAB_PORT` to `443` based on your configuration for load balancer above gitlab service, or add `clone_url` to the runner config file `/etc/gitlab-runner/config.toml`. Also, don't forget to restart docker servcie `sudo service docker restart`. + +```toml +concurrent = 1 +check_interval = 0 +shutdown_timeout = 0 + +[session_server] + session_timeout = 1800 + +[[runners]] + name = "Name of docker runner" + url = "https://gitlab.mycompany.com/" + clone_url = "https://gitlab.mycompany.com/" + id = 1 + token = "s_____________n" + token_obtained_at = 2023-07-22T01:20:41Z + token_expires_at = 0001-01-01T00:00:00Z + executor = "docker" + [runners.cache] + MaxUploadedArchiveSize = 0 + [runners.docker] + tls_verify = false + image = "docker:latest" + privileged = true + disable_entrypoint_overwrite = false + oom_kill_disable = false + disable_cache = false + volumes = ["/cache"] + shm_size = 0 +``` + +# Write .gitlab-ci.yml for go project + +For go project, you can write a gitlab ci file and test the runner. + +Here is a simple gitlab ci file `.gitlab-ci.yml` for go project: + +```yaml +# You can copy and paste this template into a new `.gitlab-ci.yml` file. +# You should not add this template to an existing `.gitlab-ci.yml` file by using the `include:` keyword. +# +# To contribute improvements to CI/CD templates, please follow the Development guide at: +# https://docs.gitlab.com/ee/development/cicd/templates.html +# This specific template is located at: +# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Go.gitlab-ci.yml + +image: golang:latest + +stages: + - test + - build + - deploy + +format: + stage: test + script: + - go fmt $(go list ./... | grep -v /vendor/) + - go vet $(go list ./... | grep -v /vendor/) + - go test -race $(go list ./... | grep -v /vendor/) + +compile: + stage: build + script: + - mkdir -p mybinaries + - go build -o mybinaries ./... + artifacts: + paths: + - mybinaries + +deploy: + stage: deploy + script: echo "Define your deployment script!" + environment: production +``` + +# Test the runner + +You can trigger ci either by committing on `main` branch or clicking `Run Pipeline` in your project's pipeline page, i.e https://gitlab.mycompany.com/myname/go-ci-test/-/pipelines + +If the pipeline is passed, your gitlab runner is runner successfully. diff --git a/src/kubernetes/cronjob/cronjob-to-restart-deployment.md b/src/kubernetes/cronjob/cronjob-to-restart-deployment.md new file mode 100644 index 0000000..4fa9d26 --- /dev/null +++ b/src/kubernetes/cronjob/cronjob-to-restart-deployment.md @@ -0,0 +1,325 @@ +# CronJob To Restart Deployment + +- [CronJob To Restart Deployment](#cronjob-to-restart-deployment) + - [Explain line by line](#explain-line-by-line) + - [ServiceAccount](#serviceaccount) + - [Role](#role) + - [RoleBinding](#rolebinding) + - [CronJob](#cronjob) + +# CronJob To Restart Deployment + +Here is an example of how to restart deployment using cronjob. + +```yaml +--- +# Service account the client will use to reset the deployment, +# by default the pods running inside the cluster can do no such things. +kind: ServiceAccount +apiVersion: v1 +metadata: + name: deployment-restart + namespace: default +--- +# allow getting status and patching only the one deployment you want to restart +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: deployment-restart + namespace: default +rules: + - apiGroups: ["apps", "extensions"] + resources: ["deployments"] + resourceNames: ["my-fast-and-robust-service"] + verbs: + # "list" and "watch" are only needed if you want to use `rollout status` + ["get", "patch", "list", "watch"] +--- +# bind the role to the service account +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: deployment-restart + namespace: default +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: deployment-restart +subjects: + - kind: ServiceAccount + name: deployment-restart + namespace: default +--- +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: deployment-restart + namespace: default +spec: + concurrencyPolicy: Forbid + # cron spec of time, here, 8 o'clock + schedule: "0 1 * * *" + jobTemplate: + spec: + backoffLimit: + # this has very low chance of failing, as all this does + # is prompt kubernetes to schedule new replica set for + # the deployment + 2 + activeDeadlineSeconds: + # timeout, makes most sense with "waiting for rollout" variant specified below + 600 + template: + spec: + serviceAccountName: + # name of the service account configured above + deployment-restart + restartPolicy: Never + containers: + - name: kubectl + image: + # probably any kubectl image will do, + # optionaly specify version, but this + # should not be necessary, as long the + # version of kubectl is new enough to + # have `rollout restart` + bitnami/kubectl + command: + - "kubectl" + - "rollout" + - "restart" + - "deployment/my-fast-and-robust-service" +``` + +这是一份 Kubernetes YAML 配置文件,用于创建一个 CronJob,定期重启指定名称的 Deployment。以下是配置文件的解释: + +1. 通过创建 ServiceAccount,确保 Kubernetes 能够在崩溃时自动重启 Deployment。 + +2. 创建一个 RBAC Role,允许使用 `get`、`patch`、`list` 和 `watch` 操作,但仅限于指定的 Deployment(即 `my-fast-and-robust-service`)。 + +3. 创建一个 RBAC RoleBinding,将上述角色与先前创建的 ServiceAccount 绑定。 + +4. 创建一个 CronJob,定义了要执行的任务和运行频率。该任务将定期启动一个 Pod,并使用 kubectl 命令重启指定的 Deployment。 + +总之,这个配置文件实现了一个可以定期重启指定 `Deployment` 的 `CronJob`,以确保 `Deployment` 在运行过程中保持稳定性和可用性。这可能会有助于解决某些资源耗尽或性能问题,以及其他可能导致 `Deployment` 失败的情况。请注意,这应该被视为一种应急措施,应该在确定没有更好的解决方案之前谨慎使用。 + +## Explain line by line + +### ServiceAccount + +好的,下面是这个 YAML 配置文件中每一行的解释: + +``` +--- +# Service account the client will use to reset the deployment, +# by default the pods running inside the cluster can do no such things. +kind: ServiceAccount +apiVersion: v1 +metadata: + name: deployment-restart + namespace: default +``` + +- `---`:指示 Kubernetes YAML 文件的开始。 + +- `# Service account the client will use to reset the deployment,`:注释,提供了正在创建的 ServiceAccount 的描述。 + +- `kind: ServiceAccount`:指定 Kubernetes 对象的类型,这里是 ServiceAccount。 + +- `apiVersion: v1`:指定 API 版本,这里是 v1。 + +- `metadata:`:定义 Kubernetes 对象的元数据,包括名称和命名空间。 + +- `name: deployment-restart`:指定创建的 ServiceAccount 的名称为 deployment-restart。 + +- `namespace: default`:指定命名空间为 default。 + +### Role + +``` +--- +# allow getting status and patching only the one deployment you want to restart +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: deployment-restart + namespace: default +rules: + - apiGroups: ["apps", "extensions"] + resources: ["deployments"] + resourceNames: ["my-fast-and-robust-service"] + verbs: + # "list" and "watch" are only needed if you want to use `rollout status` + ["get", "patch", "list", "watch"] +``` + +- `---`:指示 Kubernetes YAML 文件的开始。 + +- `# allow getting status and patching only the one deployment you want to restart`:注释,提供了所创建的 Role 的描述。 + +- `apiVersion: rbac.authorization.k8s.io/v1`:指定 RBAC API 版本,这里是 v1。 + +- `kind: Role`:指定 Kubernetes 对象类型为 Role。 + +- `metadata:`:定义 Kubernetes 对象的元数据,包括名称和命名空间。 + +- `name: deployment-restart`:指定创建的 Role 的名称为 deployment-restart。 + +- `namespace: default`:指定命名空间为 default。 + +- `rules:`:定义权限规则。 + +- `- apiGroups: ["apps", "extensions"]`:指定 API 组。这里指定了 apps 和 extensions。 + +- `resources: ["deployments"]`:指定资源种类,这里是 deployments。 + +- `resourceNames: ["my-fast-and-robust-service"]`:指定资源名称,这里是 my-fast-and-robust-service。 + +- `verbs: ["get", "patch", "list", "watch"]`:指定允许执行的操作,这里包括 get、patch、list 和 watch。 + +### RoleBinding + +``` +--- +# bind the role to the service account +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: deployment-restart + namespace: default +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: deployment-restart +subjects: + - kind: ServiceAccount + name: deployment-restart + namespace: default +``` + +- `---`:指示 Kubernetes YAML 文件的开始。 + +- `# bind the role to the service account`:注释,提供了 RoleBinding 的描述。 + +- `apiVersion: rbac.authorization.k8s.io/v1`:指定 RBAC API 版本,这里是 v1。 + +- `kind: RoleBinding`:指定 Kubernetes 对象类型为 RoleBinding。 + +- `metadata:`:定义 Kubernetes 对象的元数据,包括名称和命名空间。 + +- `name: deployment-restart`:指定创建的 RoleBinding 的名称为 deployment-restart。 + +- `namespace: default`:指定命名空间为 default。 + +- `roleRef:`:引用要绑定的角色。 + +- `apiGroup: rbac.authorization.k8s.io`:指定 RBAC API 组。 + +- `kind: Role`:指定角色类型为 Role。 + +- `name: deployment-restart`:指定要绑定的角色的名称为 deployment-restart。 + +- `subjects:`:指定要绑定角色的主体(例如 ServiceAccount)。 + +- `- kind: ServiceAccount`:指定主体对象的类型为 ServiceAccount。 + +- `name: deployment-restart`:指定主体对象的名称为 deployment-restart。 + +- `namespace: default`:指定主体对象所在的命名空间为 default。 + +### CronJob + +``` +--- +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: deployment-restart + namespace: default +spec: + concurrencyPolicy: Forbid + # cron spec of time, here, 8 o'clock + schedule: "0 1 * * *" + jobTemplate: + spec: + backoffLimit: + # this has very low chance of failing, as all this does + # is prompt kubernetes to schedule new replica set for + # the deployment + 2 + activeDeadlineSeconds: + # timeout, makes most sense with "waiting for rollout" variant specified below + 600 + template: + spec: + serviceAccountName: + # name of the service account configured above + deployment-restart + restartPolicy: Never + containers: + - name: kubectl + image: + # probably any kubectl image will do, + # optionaly specify version, but this + # should not be necessary, as long the + # version of kubectl is new enough to + # have `rollout restart` + bitnami/kubectl + command: + - "kubectl" + - "rollout" + - "restart" + - "deployment/my-fast-and-robust-service" +``` + +- `---`:指示 Kubernetes YAML 文件的开始。 + +- `apiVersion: batch/v1beta1`:指定批处理 API 版本,这里是 v1beta1。 + +- `kind: CronJob`:指定 Kubernetes 对象类型为 CronJob。 + +- `metadata:`:定义 Kubernetes 对象的元数据,包括名称和命名空间。 + +- `name: deployment-restart`:指定创建的 `CronJob` 的名称为 deployment-restart。 + +- `namespace: default`:指定命名空间为 `default`。 + +- `spec:`:定义 CronJob 的规范。 + +- `concurrencyPolicy: Forbid`:指定并发策略为 Forbid,即如果上一个任务还未完成,则不会启动新的任务。 + +- `schedule: "0 1 * * *"`:指定 CronJob 的运行频率,这里是每天的凌晨 1 点。 + +- `jobTemplate:`:定义要执行的作业模板。 + +- `spec:`:指定作业的规范。 + +- `backoffLimit:`:定义作业的退避限制,即在失败后重试此作业的次数。这里设置为 2。 + +- `activeDeadlineSeconds:`:定义作业的运行时间截止日期(以秒为单位)。这里设置为 600 秒。 + +- `template:`:定义作业的 Pod 模板。 + +- `spec:`:指定 Pod 的规范。 + +- `serviceAccountName:`:指定 Pod 使用的 ServiceAccount 的名称,这里是 deployment-restart。 + +- `restartPolicy: Never`:定义 Pod 的重启策略为 Never,即当 Pod 终止时不会自动重启。 + +- `containers:`:定义 Pod 中的容器列表。 + +- `- name: kubectl`:指定容器的名称为 kubectl。 + +- `image: bitnami/kubectl`:指定使用的 kubectl 容器镜像。 + +- `command:`:指定要在容器中执行的命令列表。 + +- `- "kubectl"`:指定要执行的第一个命令为 kubectl。 + +- `- "rollout"`:指定要执行的第二个命令为 rollout。 + +- `- "restart"`:指定要执行的第三个命令为 restart。 + +- `- "deployment/my-fast-and-robust-service"`:指定要重启的 Deployment 的名称为 `my-fast-and-robust-service`。 + +总之,这个 `YAML` 配置文件定义了 `CronJob`,并使用 kubectl 命令重启指定的 Deployment。该 CronJob 将定期运行,并确保 Deployment 在运行过程中保持稳定性和可用性。 diff --git a/src/mui/upgrade/upgrade-from-v4-to-v5.md b/src/mui/upgrade/upgrade-from-v4-to-v5.md new file mode 100644 index 0000000..4a842db --- /dev/null +++ b/src/mui/upgrade/upgrade-from-v4-to-v5.md @@ -0,0 +1,81 @@ +# Title: Upgrading Material-UI from v4 to v5: A Comprehensive Guide + + + +- [Introduction:](#introduction) +- [Step 1: Upgrade React to 17.0.0:](#step-1-upgrade-react-to-1700) +- [Step 2: Update MUI packages and peer dependencies:](#step-2-update-mui-packages-and-peer-dependencies) +- [Step 3: Run codemods:](#step-3-run-codemods) +- [Step 4: Fix broken code:](#step-4-fix-broken-code) +- [Step 5: Replace all imports:](#step-5-replace-all-imports) +- [Step 6: Test and finalize:](#step-6-test-and-finalize) +- [References:](#references) + + + +# Introduction: + +Material-UI is a popular React component library that provides a set of pre-built UI components for building modern and responsive web applications. With the release of Material-UI v5, there have been significant changes and improvements, making it essential for developers to upgrade from v4 to v5. In this blog post, we will walk you through the step-by-step process of upgrading Material-UI to its latest version. + +# Step 1: Upgrade React to 17.0.0: + +To start the upgrade process, it is necessary to update React to version 17.0.0 or above. This can be done using the following command: + +```bash +yarn upgrade @material-ui/core@^4.11.2 react@^17.0.0 +``` + +# Step 2: Update MUI packages and peer dependencies: + +Next, we need to update the Material-UI packages and their peer dependencies. Run the following commands to install the required packages: + +```bash +yarn add @mui/material @mui/styles +yarn add @mui/lab +yarn add @mui/icons-material +yarn add @emotion/react @emotion/styled +``` + +# Step 3: Run codemods: + +Material-UI provides codemods that automatically adjust your code to account for breaking changes in v5. These codemods help in migrating your codebase efficiently. Run the following command to apply the preset-safe codemod: + +```bash +npx @mui/codemod v5.0.0/preset-safe +``` + +Additionally, you can run specific codemods for individual components or pages if needed. For example: + +```bash +npx @mui/codemod v5.0.0/preset-safe components +npx @mui/codemod v5.0.0/preset-safe pages +``` + +# Step 4: Fix broken code: + +After running the codemods, it's important to review your codebase for any broken code. One common issue is the usage of the `theme.spacing()` function, which has changed in v5. Replace instances of `theme.spacing(2)` with `2`, `theme.spacing(4)` with `4`, and so on, to fix this issue. + +# Step 5: Replace all imports: + +With the release of v5, the package names have changed from `@material-ui/*` to `@mui/*`. To ensure compatibility with the latest version, replace all imports accordingly. Here are some examples: + +```bash +yarn remove @material-ui/core +yarn remove @material-ui/icons +yarn remove @material-ui/lab +yarn remove @material-ui/pickers + +yarn remove @mui/x-data-grid +yarn add @mui/x-data-grid +``` + +# Step 6: Test and finalize: + +After completing the above steps, thoroughly test your application to ensure that it runs without any errors. Make any necessary adjustments or fixes as required. Once you are confident that your application is functioning correctly, commit the changes and finalize the upgrade process. + +Conclusion: +Upgrading Material-UI from v4 to v5 is an important step to take advantage of the latest features, bug fixes, and improvements. By following the steps outlined in this guide, you can smoothly upgrade your Material-UI-based application to the latest version. Remember to thoroughly test your application after the upgrade to ensure everything is functioning as expected. Happy coding with Material-UI v5! + +# References: + +- Material-UI Migration Guide: [Migrating to v5: getting started](https://mui.com/material-ui/migration/migration-v4/) diff --git a/src/react/functional-component/render-all-chartjs-charts.md b/src/react/functional-component/render-all-chartjs-charts.md new file mode 100644 index 0000000..d1b03c9 --- /dev/null +++ b/src/react/functional-component/render-all-chartjs-charts.md @@ -0,0 +1,448 @@ +# Render all chartjs charts in react typescript tailwindcss projects + +In this tutorial, we'll show you how to render all chartjs charts in React project. + +# Table of Contents + + + +- [Define all chartjs charts](#define-all-chartjs-charts) +- [Write a funcitonal component](#write-a-funcitonal-component) +- [Write a chartjs wrapper](#write-a-chartjs-wrapper) +- [Final Code](#final-code) +- [Demo](#demo) +- [Optimize](#optimize) + * [chatgpt says](#chatgpt-says) + + + +# Define all chartjs charts + +We copy all chartjs official example codes and put them in `component/chartjsofficial` directory. We also define title and component for each example chart. + +```jsx +import { VerticalBarChart } from "@/component/chartjsofficial/verticalbarchart"; +import { HorizontalBarChart } from "@/component/chartjsofficial/horizontalbarchart"; +import { StackedBarChart } from "@/component/chartjsofficial/stackedbarchart"; +import { GroupedBarChart } from "@/component/chartjsofficial/groupedbarchart"; +import { AreaChart } from "@/component/chartjsofficial/areachart"; +import { LineChart } from "@/component/chartjsofficial/linechart"; +import { MultiAxisLineChart } from "@/component/chartjsofficial/multiaxislinechart"; +import { PieChart } from "@/component/chartjsofficial/piechart"; +import { DoughnutChart } from "@/component/chartjsofficial/doughnutchart"; +import { PolarAreaChart } from "@/component/chartjsofficial/polarareachart"; +import { RadarChart } from "@/component/chartjsofficial/radarchart"; +import { ScatterChart } from "@/component/chartjsofficial/scatterchart"; +import { BubbleChart } from "@/component/chartjsofficial/bubblechart"; +import { MultiTypeChart } from "@/component/chartjsofficial/multitypechart"; +import { ChartEvents } from "@/component/chartjsofficial/chartevents"; +import { ChartRef } from "@/component/chartjsofficial/chartref"; +import { GradientChart } from "@/component/chartjsofficial/gradientchart"; +import { ChartEventsSingleDataset } from "@/component/chartjsofficial/charteventssingledataset"; +import { ChartEventsSingleDatasetOutsideDatasource } from "@/component/chartjsofficial/charteventssingledatasetoutsidedatasource"; + +const components = [ + { + title: "VerticalBarChart", + component: VerticalBarChart, + }, + { + title: "HorizontalBarChart", + component: HorizontalBarChart, + }, + { + title: "StackedBarChart", + component: StackedBarChart, + }, + { + title: "GroupedBarChart", + component: GroupedBarChart, + }, + { + title: "AreaChart", + component: AreaChart, + }, + { + title: "LineChart", + component: LineChart, + }, + { + title: "MultiAxisLineChart", + component: MultiAxisLineChart, + }, + { + title: "DoughnutChart", + component: DoughnutChart, + }, + { + title: "PolarAreaChart", + component: PolarAreaChart, + }, + { + title: "RadarChart", + component: RadarChart, + }, + { + title: "ScatterChart", + component: ScatterChart, + }, + { + title: "BubbleChart", + component: BubbleChart, + }, + { + title: "ScatterChart", + component: ScatterChart, + }, + { + title: "MultiTypeChart", + component: MultiTypeChart, + }, + { + title: "ChartEvents", + component: ChartEvents, + }, + { + title: "ChartRef", + component: ChartRef, + }, + { + title: "GradientChart", + component: GradientChart, + }, + { + title: "ChartEventsSingleDataset", + component: ChartEventsSingleDataset, + }, +]; +``` + +# Write a funcitonal component + +In order to render all components, we write a functional component to take array of component with title and render them in one place. + +```jsx +type Component = { + title: string, + component: React.FunctionComponent, +}; +type ChartProps = { + components: Component[], +}; + +const ComponentWrapper: React.FC = ({ components }) => { + return ( +

+ ); +}; +``` + +# Write a chartjs wrapper + +In order to add title for each chart, we write a functional component to wrapper up every chart with a `h1`. Note we define the `h1` style using tailwindcss, setting div size(`h-96`) and text size(`text-3xl`), etc. + +```jsx +export const ChartWrapper: React.FC<{ + title: string, + children: React.ReactNode, +}> = ({ title, children }) => { + return ( +
+

{title}

+ {children} +
+ ); +}; +``` + +# Final Code + +First, import all chartjs components and define components to render. + +```jsx +import { VerticalBarChart } from "@/component/chartjsofficial/verticalbarchart"; +import { HorizontalBarChart } from "@/component/chartjsofficial/horizontalbarchart"; +import { StackedBarChart } from "@/component/chartjsofficial/stackedbarchart"; +import { GroupedBarChart } from "@/component/chartjsofficial/groupedbarchart"; +import { AreaChart } from "@/component/chartjsofficial/areachart"; +import { LineChart } from "@/component/chartjsofficial/linechart"; +import { MultiAxisLineChart } from "@/component/chartjsofficial/multiaxislinechart"; +import { PieChart } from "@/component/chartjsofficial/piechart"; +import { DoughnutChart } from "@/component/chartjsofficial/doughnutchart"; +import { PolarAreaChart } from "@/component/chartjsofficial/polarareachart"; +import { RadarChart } from "@/component/chartjsofficial/radarchart"; +import { ScatterChart } from "@/component/chartjsofficial/scatterchart"; +import { BubbleChart } from "@/component/chartjsofficial/bubblechart"; +import { MultiTypeChart } from "@/component/chartjsofficial/multitypechart"; +import { ChartEvents } from "@/component/chartjsofficial/chartevents"; +import { ChartRef } from "@/component/chartjsofficial/chartref"; +import { GradientChart } from "@/component/chartjsofficial/gradientchart"; +import { ChartEventsSingleDataset } from "@/component/chartjsofficial/charteventssingledataset"; +import { ChartEventsSingleDatasetOutsideDatasource } from "@/component/chartjsofficial/charteventssingledatasetoutsidedatasource"; + +const components = [ + { + title: "VerticalBarChart", + component: VerticalBarChart, + }, + { + title: "HorizontalBarChart", + component: HorizontalBarChart, + }, + { + title: "StackedBarChart", + component: StackedBarChart, + }, + { + title: "GroupedBarChart", + component: GroupedBarChart, + }, + { + title: "AreaChart", + component: AreaChart, + }, + { + title: "LineChart", + component: LineChart, + }, + { + title: "MultiAxisLineChart", + component: MultiAxisLineChart, + }, + { + title: "DoughnutChart", + component: DoughnutChart, + }, + { + title: "PolarAreaChart", + component: PolarAreaChart, + }, + { + title: "RadarChart", + component: RadarChart, + }, + { + title: "ScatterChart", + component: ScatterChart, + }, + { + title: "BubbleChart", + component: BubbleChart, + }, + { + title: "ScatterChart", + component: ScatterChart, + }, + { + title: "MultiTypeChart", + component: MultiTypeChart, + }, + { + title: "ChartEvents", + component: ChartEvents, + }, + { + title: "ChartRef", + component: ChartRef, + }, + { + title: "GradientChart", + component: GradientChart, + }, + { + title: "ChartEventsSingleDataset", + component: ChartEventsSingleDataset, + }, +]; +``` + +Next, write a function called `ChartWrapper` to wrapper up react chartjs chart components with a `h1` as the title. + +```jsx +export const ChartWrapper: React.FC<{ + title: string, + children: React.ReactNode, +}> = ({ title, children }) => { + return ( +
+

{title}

+ {children} +
+ ); +}; +``` + +Then, write a functional component to take a arrays of components and render them use `ChartWrapper` component. + +```jsx +type Component = { + title: string, + component: React.FunctionComponent, +}; +type ChartProps = { + components: Component[], +}; + +const ComponentWrapper: React.FC = ({ components }) => { + return ( +
+ {components.map((component, index) => { + return ( + + + + ); + })} +
+ ); +}; +``` + +Finally, in `App` component, we iterate the components and render them in a div with `grid` class. + +```jsx +export default function App() { + return ( +
+ + ChartJS + NextJS + TailwindCSS + + + + +
+
+

+ ChartJS + NextJS + TailwindCSS +

+
+
+ {} +
+
+
+
+
+ ); +} +``` + +# Demo + +![chartjs-react-chartjs-2-nextjs-tailwind-typescript-using-grid](https://user-images.githubusercontent.com/74223747/224673738-9edb7c6d-5e49-44a2-9bb0-00fa9eb5f608.png) + +# Optimize + +Note, for `ComponentWrapper` function component, it's more verbose. We could write it in another way. + +Here is the old way. + +```jsx +// NOTE: OK but verbose +const ComponentWrapper: React.FC = ({ components }) => { + return ( +
+ {components.map((c, index) => { + return ( + + + + ); + })} +
+ ); +}; +``` + +The new way: + +```jsx +// NOTE: concise +const ComponentWrapperChatGPTAgain: React.FC = ({ components }) => { + return ( +
+ {components.map((c, index) => { + const { title, component: C } = c; + + return ( + + + + ); + })} +
+ ); +}; +``` + +To render list of charts components with titles and properties, we wrote two React component functions. While both functions produce the same output, their implementation approaches are different. + +The first function `ComponentWrapper` is a good method, but the downside is that it is quite verbose. It uses curly braces and a return statement to wrap the component inside ``, and it also references `c.title` and `c.component` multiple times within the function body. + +The second function `ComponentWrapperChatGPTAgain` is a more concise implementation. It uses ES6 destructuring syntax to get title and component from the components object, which avoids repeated use of `c.title` and `c.component`. Additionally, it uses a more descriptive variable name `C` for the component. + +Clearly, the second implementation is more succinct, easier to read and maintain, and also makes it easier to add new properties or methods. Therefore, we recommend using the second function `ComponentWrapperChatGPTAgain` as a more efficient programming practice. + +To summarize, the primary difference between these two implementation methods is that the first one is verbose, which makes it difficult to read and maintain, while the second one is more concise and easier to read and maintain. The second function also uses destructuring, which is a useful syntax for improving readability by eliminating repetition. By using a more concise and readable implementation, you can streamline your code and make it more efficient. + +## chatgpt says + +Comparing Two React Component Functions + +In this post, we will compare and contrast two React component functions that are used to render a list of chart components with titles and properties. While both functions ultimately produce the same result, their implementation approaches are different. + +Function 1: ComponentWrapper + +```jsx +const ComponentWrapper: React.FC = ({ components }) => { + return ( +
+ {components.map((c, index) => { + return ( + + + + ); + })} +
+ ); +}; +``` + +This function is a good approach, but it is quite verbose. It uses curly braces and a return statement to wrap the component inside ``, and it also references `c.title` and `c.component` multiple times within the function body. It might be a bit difficult to read and maintain for developers, especially with more complex components. + +Function 2: ComponentWrapperChatGPTAgain + +```jsx +const ComponentWrapperChatGPTAgain: React.FC = ({ components }) => { + return ( +
+ {components.map((c, index) => { + const { title, component: C } = c; + + return ( + + + + ); + })} +
+ ); +}; +``` + +This function, on the other hand, is a more concise implementation. It uses ES6 destructuring syntax to get title and component from the components object, which avoids repeated use of `c.title` and `c.component`. Additionally, it uses a more descriptive variable name `C` for the component. + +The second implementation is more succinct, easier to read and maintain, and also makes it easier to add new properties or methods in the future. Therefore, we recommend using the second function `ComponentWrapperChatGPTAgain` as a more efficient programming practice. + +Conclusion + +The primary difference between these two implementation methods is that the first one is verbose, which makes it difficult to read and maintain, while the second one is more concise and easier to read and maintain. The second function also uses destructuring, which is a useful syntax for improving readability by eliminating repetition. By using a concise, readable implementation, code can be streamlined and more efficient. diff --git a/src/rust/actix/prometheus-support-to-actix-web.md b/src/rust/actix/prometheus-support-to-actix-web.md new file mode 100644 index 0000000..e284235 --- /dev/null +++ b/src/rust/actix/prometheus-support-to-actix-web.md @@ -0,0 +1,163 @@ +# prometheus support for actix-web project + +- [Init an empty actix-web project with tokio runtime](#init-an-empty-actix-web-project-with-tokio-runtime) +- [Add prometheus support actix-web project](#add-prometheus-support-actix-web-project) +- [Enable process features](#enable-process-features) +- [How to use](#how-to-use) +- [Run actix-web server](#run-actix-web-server) +- [Request metrics endpoint](#request-metrics-endpoint) + +## Init an empty actix-web project with tokio runtime + +```bash +# init project +cargo init actix-web-t +# add dependencies +cargo add actix-web +cargo add tokio --features full +``` + +## Add prometheus support actix-web project + +```bash +cargo add actix_web_prometheus +``` + +`actix-web-prometheus` is a middleware inspired by and forked from actix-web-prom. By default three metrics are tracked (this assumes the namespace `actix_web_prometheus`): + +- `actix_web_prometheus_incoming_requests` (labels: endpoint, method, status): the total number of HTTP requests handled by the actix `HttpServer`. +- `actix_web_prometheus_response_code` (labels: endpoint, method, statuscode, type): Response codes of all HTTP requests handled by the actix `HttpServer`. +- `actix_web_prometheus_response_time` (labels: endpoint, method, status): Total the request duration of all HTTP requests handled by the actix `HttpServer`. + +## Enable process features + +You could also enable `process` features when adding `actix_web_prometheus` crate, which means process metrics will also be collected. + +```bash +cargo add actix_web_prometheus --features process +``` + +Output: + +```bash + Updating crates.io index +warning: translating `actix_web_prometheus` to `actix-web-prometheus` + Adding actix-web-prometheus v0.1.2 to dependencies. + Features: + + process +``` + +## How to use + +Here is an simple example of how to integrate this middleware into `actix-web` project. + +`main.rs` + +```rust +use actix_web::{http, web, App, HttpServer, Responder, Result, HttpResponse}; +use actix_web_prometheus::PrometheusMetricsBuilder; +use serde::{Deserialize, Serialize}; + + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + let prometheus = PrometheusMetricsBuilder::new("api") + .endpoint("/metrics") + .build() + .unwrap(); + + HttpServer::new(move || { + App::new() + .wrap(prometheus.clone()) + }) + .bind(("127.0.0.1", 8080))? + .run() + .await +} +``` + +## Run actix-web server + +```bash +cargo run +``` + +Output: + +```bash +warning: unused imports: `HttpResponse`, `Responder`, `Result`, `http`, `web` + --> src/main.rs:1:17 + | +1 | use actix_web::{http, web, App, HttpServer, Responder, Result, HttpResponse}; + | ^^^^ ^^^ ^^^^^^^^^ ^^^^^^ ^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` on by default + +warning: unused imports: `Deserialize`, `Serialize` + --> src/main.rs:3:13 + | +3 | use serde::{Deserialize, Serialize}; + | ^^^^^^^^^^^ ^^^^^^^^^ + +warning: `actix-web-t` (bin "actix-web-t") generated 2 warnings + Finished dev [unoptimized + debuginfo] target(s) in 0.26s + Running `target/debug/actix-web-t` +``` + +## Request metrics endpoint + +Build and run actix-web project, we can send request to `/metrics` endpoint. + +```bash +curl 0:8080/metrics +``` + +Ouput: + +```bash +# HELP api_incoming_requests Incoming Requests +# TYPE api_incoming_requests counter +api_incoming_requests{endpoint="/metrics",method="GET",status="200"} 28 +# HELP api_response_code Response Codes +# TYPE api_response_code counter +api_response_code{endpoint="/metrics",method="GET",statuscode="200",type="200"} 28 +# HELP api_response_time Response Times +# TYPE api_response_time histogram +api_response_time_bucket{endpoint="/metrics",method="GET",status="200",le="0.005"} 28 +api_response_time_bucket{endpoint="/metrics",method="GET",status="200",le="0.01"} 28 +api_response_time_bucket{endpoint="/metrics",method="GET",status="200",le="0.025"} 28 +api_response_time_bucket{endpoint="/metrics",method="GET",status="200",le="0.05"} 28 +api_response_time_bucket{endpoint="/metrics",method="GET",status="200",le="0.1"} 28 +api_response_time_bucket{endpoint="/metrics",method="GET",status="200",le="0.25"} 28 +api_response_time_bucket{endpoint="/metrics",method="GET",status="200",le="0.5"} 28 +api_response_time_bucket{endpoint="/metrics",method="GET",status="200",le="1"} 28 +api_response_time_bucket{endpoint="/metrics",method="GET",status="200",le="2.5"} 28 +api_response_time_bucket{endpoint="/metrics",method="GET",status="200",le="5"} 28 +api_response_time_bucket{endpoint="/metrics",method="GET",status="200",le="10"} 28 +api_response_time_bucket{endpoint="/metrics",method="GET",status="200",le="+Inf"} 28 +api_response_time_sum{endpoint="/metrics",method="GET",status="200"} 0.03155173499999999 +api_response_time_count{endpoint="/metrics",method="GET",status="200"} 28 +# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds. +# TYPE process_cpu_seconds_total counter +process_cpu_seconds_total 66 +# HELP process_max_fds Maximum number of open file descriptors. +# TYPE process_max_fds gauge +process_max_fds 50000 +# HELP process_open_fds Number of open file descriptors. +# TYPE process_open_fds gauge +process_open_fds 23 +# HELP process_resident_memory_bytes Resident memory size in bytes. +# TYPE process_resident_memory_bytes gauge +process_resident_memory_bytes 6410240 +# HELP process_start_time_seconds Start time of the process since unix epoch in seconds. +# TYPE process_start_time_seconds gauge +process_start_time_seconds 1677656707 +# HELP process_threads Number of OS threads in the process. +# TYPE process_threads gauge +process_threads 3 +# HELP process_virtual_memory_bytes Virtual memory size in bytes. +# TYPE process_virtual_memory_bytes gauge +process_virtual_memory_bytes 165015552 +``` + +Notice, on MacOS, process metrics are not exported. diff --git a/src/rust/actix/send-http-request-in-handle-function-and-started-function-when-using-actix-crate.md b/src/rust/actix/send-http-request-in-handle-function-and-started-function-when-using-actix-crate.md new file mode 100644 index 0000000..2396bc5 --- /dev/null +++ b/src/rust/actix/send-http-request-in-handle-function-and-started-function-when-using-actix-crate.md @@ -0,0 +1,879 @@ +# Send Http Request in Handle function or in started function When Using Actix crate + +- [Send Http Request in Handle function](#send-http-request-in-handle-function) + - [Return type `Result`](#return-type-result) + - [Return type `Result`](#return-type-result) + - [Return type `Result`](#return-type-result) + - [Return type `Result`](#return-type-result) +- [Send Http Request in started function](#send-http-request-in-started-function) +- [Full source code](#full-source-code) + - [Send http request in `handle` function](#send-http-request-in-handle-function) + - [Send http request in `started` function](#send-http-request-in-started-function) +- [Appendix](#appendix) + - [Actor trait](#actor-trait) + +# Send Http Request in Handle function + +When using actors to develop concurrent applications, you may need to run asynchronous functions, such as sending HTTP requests, when an actor is started or when handling specific messages. + +We know there's a method called `started` when implementing `Actor` trait. The `Actor` trait is defined as follows: + +```rust +pub trait Actor: Sized + Unpin + 'static { + /// Actor execution context type + type Context: ActorContext; + + /// Called when an actor gets polled the first time. + fn started(&mut self, ctx: &mut Self::Context) {} + + /// Called after an actor is in `Actor::Stopping` state. + /// + /// There can be several reasons for stopping: + /// + /// - `Context::stop` gets called by the actor itself. + /// - All addresses to the current actor get dropped and no more + /// evented objects are left in the context. + /// + /// An actor can return from the stopping state to the running + /// state by returning `Running::Continue`. + fn stopping(&mut self, ctx: &mut Self::Context) -> Running { + Running::Stop + } + + /// Called after an actor is stopped. + /// + /// This method can be used to perform any needed cleanup work or + /// to spawn more actors. This is the final state, after this + /// method got called, the actor will be dropped. + fn stopped(&mut self, ctx: &mut Self::Context) {} + + /// Start a new asynchronous actor, returning its address. + fn start(self) -> Addr + where + Self: Actor>, + { + Context::new().run(self) + } + + /// Construct and start a new asynchronous actor, returning its + /// address. + /// + /// This is constructs a new actor using the `Default` trait, and + /// invokes its `start` method. + fn start_default() -> Addr + where + Self: Actor> + Default, + { + Self::default().start() + } + + /// Start new actor in arbiter's thread. + fn start_in_arbiter(wrk: &ArbiterHandle, f: F) -> Addr + where + Self: Actor>, + F: FnOnce(&mut Context) -> Self + Send + 'static, + { + let (tx, rx) = channel::channel(DEFAULT_CAPACITY); + + // create actor + wrk.spawn_fn(move || { + let mut ctx = Context::with_receiver(rx); + let act = f(&mut ctx); + let fut = ctx.into_future(act); + + actix_rt::spawn(fut); + }); + + Addr::new(tx) + } + + /// Start a new asynchronous actor given a `Context`. + /// + /// Use this method if you need the `Context` object during actor + /// initialization. + fn create(f: F) -> Addr + where + Self: Actor>, + F: FnOnce(&mut Context) -> Self, + { + let mut ctx = Context::new(); + let act = f(&mut ctx); + ctx.run(act) + } +} +``` + +The `started` function will be called when actor is started, but if we call async function in `started` function(e.g. sending http request), we'll get an error: + +```rust +error[E0728]: `await` is only allowed inside `async` functions and blocks + --> src/bin/call-async-in-non-async-function.rs:25:57 + | +22 | / fn handle(&mut self, _: Msg, _: &mut Context) -> Self::Result { +23 | | // async move { Ok(()) } +24 | | +25 | | let response = reqwest::get("https://hyper.rs").await.unwrap(); + | | ^^^^^ only allowed inside `async` functions and blocks +... | +35 | | // }) +36 | | } + | |_____- this is not `async` + +For more information about this error, try `rustc --explain E0728`. +warning: `actix_example` (bin "call-async-in-non-async-function") generated 6 warnings +error: could not compile `actix_example` (bin "call-async-in-non-async-function") due to previous error; 6 warnings emitted +``` + +In Rust, `await` can only be used _within_ an async function or an async block. You can refer to [Async book](https://rust-lang.github.io/async-book/03_async_await/01_chapter.html) for more details. + +The solution is easy, I'll explain it step by step. + +## Return type `Result<(), ()>` + +Let's start with calling async function or async block in `handle` method. + +We can specify the result to be a `ResponseFuture>` and wrapper async block with `Box::pin`. + +```rust +#[derive(Message)] +#[rtype(result = "Result<(), ()>")] +struct Msg; + +struct MyActor2; + +impl Actor for MyActor2 { + type Context = Context; +} + +impl Handler for MyActor2 { + type Result = ResponseFuture>; + + fn handle(&mut self, _: Msg, _: &mut Context) -> Self::Result { + Box::pin(async move { + // Some async computation + println!("Box::pin called"); + Ok(()) + }) + } +} +``` + +As we use `ResponseFuture>` type in `Handler` trait's associated type `Result`, we can return a Box Future using `Box::pin` function in `handle` method. + +## Return type `Result` + +Now, let's change return type from `Result<(), ()>` to `Result`, which will return a `usize` from async block. + +```rust +#[derive(Message)] +#[rtype(result = "Result")] +struct Msg3; + +struct MyActor3; + +impl Actor for MyActor3 { + type Context = Context; +} + +impl Handler for MyActor3 { + type Result = ResponseActFuture>; + + fn handle(&mut self, _: Msg3, _: &mut Context) -> Self::Result { + Box::pin( + async { + println!("will return 42"); + // Some async computation + 42 + } + .into_actor(self) // converts future to ActorFuture + .map(|res, _act, _ctx| { + println!("map"); + // Do some computation with actor's state or context + Ok(res) + }), + ) + } +} +``` + +We need to change in 3 places: + +- Using `#[rtype(result = "Result")]` macro in `struct Msg3` +- Change associated type from `ResponseActFuture>;` to `ResponseActFuture>;` +- Change async block to return a value of `usize` + +## Return type `Result` + +If we care about the status code from http response, what should we do? Obviousely, we can declare a `Result` type. Here `u16` represents the status code from http response. + +```rust +#[derive(Message)] +#[rtype(result = "Result")] +// return http status code +struct Msg4; + +struct MyActor4; + +impl Actor for MyActor4 { + type Context = Context; +} + +impl Handler for MyActor4 { + // type Result = ResponseActFuture>; + type Result = ResponseActFuture>; + + fn handle(&mut self, _: Msg4, _: &mut Context) -> Self::Result { + // let res = reqwest::get("https://hyper.rs").await?; + // println!("Status: {}", res.status()); + // let body = res.text().await?; + + Box::pin( + async { + println!("will return 42"); + let status_code = match reqwest::get("https://hyper.rs").await { + Ok(response) => { + println!("Got status from hyper.rs {}", response.status()); + response.status().as_u16() + }, + Err(err) => { + println!("get response error : {err}"); + 42 as u16 + }, + }; + status_code + } + .into_actor(self) // converts future to ActorFuture + .map(|res, _act, _ctx| { + println!("result in map process : {res}"); + // Do some computation with actor's state or context + Ok(res) + }), + ) + } +} +``` + +In async block, we return status code using `response.status().as_u16()`. + +## Return type `Result` + +What if we want to use the response body, what should we do? It's quite easy to change from `u16` to `String`. The code looks like this: + +```rust +#[derive(Message)] +#[rtype(result = "Result")] +// return http reponse body +struct Msg5; + +struct MyActor5; + +impl Actor for MyActor5 { + type Context = Context; +} + +impl Handler for MyActor5 { + // type Result = ResponseActFuture>; + type Result = ResponseActFuture>; + + fn handle(&mut self, _: Msg5, _: &mut Context) -> Self::Result { + // let res = reqwest::get("https://hyper.rs").await?; + // println!("Status: {}", res.status()); + // let body = res.text().await?; + + Box::pin( + async { + let status_code = match reqwest::get("https://hyper.rs").await { + Ok(response) => { + println!("Reponse Ok from hyper.rs {}", response.status()); + match response.text().await { + Ok(body) => body, + Err(err) => { + format!("Convert Reposne to string error : {err}") + } + } + }, + Err(err) => { + format!("Reposne error from hyper.rs, error : {err}") + }, + }; + status_code + } + .into_actor(self) // converts future to ActorFuture + .map(|res, _act, _ctx| { + println!("result in map process : {res}"); + // Do some computation with actor's state or context + Ok(res) + }), + ) + } +} +``` + +Now, we use `response.text().await` to convert reponse to string and return the response body for later use. + +# Send Http Request in started function + +If we want to store some state in actor and initialize it when actor is started, we can use `context.wait` to wait an async block, turn it into an actor through `into_actor` and store the return value of async block in `then` method. + +```rust +#[derive(Clone)] +struct MyActor { + status_code: Option, +} + +impl MyActor { + fn print_status_code(&mut self, context: &mut Context) { + println!("status code: {:?}", self.status_code); + } +} + +impl Actor for MyActor { + type Context = Context; + + fn started(&mut self, context: &mut Context) { + println!("In started"); + // ✅NOTE: This will run + context.wait( + async move { + // send http reqwest + let status_code = match reqwest::get("https://hyper.rs").await { + Ok(response) => { + println!( + "Got status from hyper.rs {}", + response.status() + ); + response.status().as_u16() + } + Err(err) => { + println!("get response error : {err}"); + 42 as u16 + } + }; + println!("status code: {status_code}"); + + status_code + } + .into_actor(self) + .then(|output, s, ctx| { + s.status_code = Some(output); + fut::ready(()) + }), + ); + + IntervalFunc::new(Duration::from_millis(5000), Self::print_status_code) + .finish() + .spawn(context); + + context.run_later(Duration::from_millis(20000), |_, _| { + System::current().stop() + }); + } +} +``` + +In this example, we store status code as `Option` in `MyActor` and save it then method from `ActorFutureExt` trait: + +```rust +fn started(&mut self, context: &mut Context) { + context.wait( + async move { + // send http reqwest + let status_code = match reqwest::get("https://hyper.rs").await { + Ok(response) => { + response.status().as_u16() + } + Err(err) => { + 42 as u16 + } + }; + status_code + } + .into_actor(self) + .then(|output, s, ctx| { + s.status_code = Some(output); + fut::ready(()) + }), + ); +} +``` + +Here is the definition of `ActorFutureExt` trait. + +```rust +pub trait ActorFutureExt: ActorFuture
{ + /// Map this future's result to a different type, returning a new future of + /// the resulting type. + fn map(self, f: F) -> Map + where + F: FnOnce(Self::Output, &mut A, &mut A::Context) -> U, + Self: Sized, + { + Map::new(self, f) + } + + /// Chain on a computation for when a future finished, passing the result of + /// the future to the provided closure `f`. + fn then(self, f: F) -> Then + where + F: FnOnce(Self::Output, &mut A, &mut A::Context) -> Fut, + Fut: ActorFuture, + Self: Sized, + { + then::new(self, f) + } + + /// Add timeout to futures chain. + /// + /// `Err(())` returned as a timeout error. + fn timeout(self, timeout: Duration) -> Timeout + where + Self: Sized, + { + Timeout::new(self, timeout) + } + + /// Wrap the future in a Box, pinning it. + /// + /// A shortcut for wrapping in [`Box::pin`]. + fn boxed_local(self) -> LocalBoxActorFuture + where + Self: Sized + 'static, + { + Box::pin(self) + } +} +``` + +# Full source code + +## Send http request in `handle` function + +```rust +use actix::prelude::*; +use anyhow::Result; +use futures::prelude::*; +use tokio::time::{sleep, Duration}; + +#[derive(Message)] +#[rtype(result = "Result<(), ()>")] +struct Msg; + +struct MyActor2; + +impl Actor for MyActor2 { + type Context = Context; +} + +impl Handler for MyActor2 { + type Result = ResponseFuture>; + + fn handle(&mut self, _: Msg, _: &mut Context) -> Self::Result { + Box::pin(async move { + // Some async computation + println!("Box::pin called"); + Ok(()) + }) + } +} + +#[derive(Message)] +#[rtype(result = "Result")] +struct Msg3; + +struct MyActor3; + +impl Actor for MyActor3 { + type Context = Context; +} + +impl Handler for MyActor3 { + type Result = ResponseActFuture>; + + fn handle(&mut self, _: Msg3, _: &mut Context) -> Self::Result { + Box::pin( + async { + println!("will return 42"); + // Some async computation + 42 + } + .into_actor(self) // converts future to ActorFuture + .map(|res, _act, _ctx| { + println!("map"); + // Do some computation with actor's state or context + Ok(res) + }), + ) + } +} + +#[derive(Message)] +#[rtype(result = "Result")] +// return http status code +struct Msg4; + +struct MyActor4; + +impl Actor for MyActor4 { + type Context = Context; +} + +impl Handler for MyActor4 { + // type Result = ResponseActFuture>; + type Result = ResponseActFuture>; + + fn handle(&mut self, _: Msg4, _: &mut Context) -> Self::Result { + // let res = reqwest::get("https://hyper.rs").await?; + // println!("Status: {}", res.status()); + // let body = res.text().await?; + + Box::pin( + async { + println!("will return 42"); + let status_code = match reqwest::get("https://hyper.rs").await { + Ok(response) => { + println!("Got status from hyper.rs {}", response.status()); + response.status().as_u16() + }, + Err(err) => { + println!("get response error : {err}"); + 42 as u16 + }, + }; + status_code + } + .into_actor(self) // converts future to ActorFuture + .map(|res, _act, _ctx| { + println!("result in map process : {res}"); + // Do some computation with actor's state or context + Ok(res) + }), + ) + } +} + +#[derive(Message)] +#[rtype(result = "Result")] +// return http reponse body +struct Msg5; + +struct MyActor5; + +impl Actor for MyActor5 { + type Context = Context; +} + +impl Handler for MyActor5 { + // type Result = ResponseActFuture>; + type Result = ResponseActFuture>; + + fn handle(&mut self, _: Msg5, _: &mut Context) -> Self::Result { + // let res = reqwest::get("https://hyper.rs").await?; + // println!("Status: {}", res.status()); + // let body = res.text().await?; + + Box::pin( + async { + let status_code = match reqwest::get("https://hyper.rs").await { + Ok(response) => { + println!("Reponse Ok from hyper.rs {}", response.status()); + match response.text().await { + Ok(body) => body, + Err(err) => { + format!("Convert Reposne to string error : {err}") + } + } + }, + Err(err) => { + format!("Reposne error from hyper.rs, error : {err}") + }, + }; + status_code + } + .into_actor(self) // converts future to ActorFuture + .map(|res, _act, _ctx| { + println!("result in map process : {res}"); + // Do some computation with actor's state or context + Ok(res) + }), + ) + } +} + +fn main() -> Result<()> { + let mut sys = actix::System::new(); + + sys.block_on(async { + // let _addr = MyActor {}.start(); + // let _addr = MyActor2 {}.start(); + // let addr = MyActor3 {}.start(); + // addr.do_send(Msg3 {}) + // OK + // let addr = MyActor4 {}.start(); + // addr.do_send(Msg4 {}) + // OK + let addr = MyActor5 {}.start(); + addr.do_send(Msg5 {}) + }); + sys.run()?; + + Ok(()) +} +``` + +## Send http request in `started` function + +```rust +use actix::prelude::*; +use actix::utils::IntervalFunc; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::oneshot::channel; +use tokio::sync::Mutex; + +#[derive(Clone)] +struct MyActor { + status_code: Option, +} + +impl MyActor { + fn tick(&mut self, context: &mut Context) { + println!("tick"); + } + + fn print_status_code(&mut self, context: &mut Context) { + println!("status code: {:?}", self.status_code); + } +} + +impl Actor for MyActor { + type Context = Context; + + fn started(&mut self, context: &mut Context) { + println!("In started"); + // ✅NOTE: This will run + context.wait( + async move { + // send http reqwest + let status_code = match reqwest::get("https://hyper.rs").await { + Ok(response) => { + println!( + "Got status from hyper.rs {}", + response.status() + ); + response.status().as_u16() + } + Err(err) => { + println!("get response error : {err}"); + 42 as u16 + } + }; + println!("status code: {status_code}"); + + status_code + } + .into_actor(self) + .then(|output, s, ctx| { + s.status_code = Some(output); + fut::ready(()) + }), + ); + + IntervalFunc::new(Duration::from_millis(5000), Self::print_status_code) + .finish() + .spawn(context); + + context.run_later(Duration::from_millis(20000), |_, _| { + System::current().stop() + }); + } +} + +fn main() { + let mut sys = System::new(); + let addr = sys.block_on(async { MyActor { status_code: None }.start() }); + sys.run(); +} +``` + +# Appendix + +## Actor trait + +````rust +/// Actors are objects which encapsulate state and behavior. +/// +/// Actors run within a specific execution context +/// [`Context`](struct.Context.html). The context object is available +/// only during execution. Each actor has a separate execution +/// context. The execution context also controls the lifecycle of an +/// actor. +/// +/// Actors communicate exclusively by exchanging messages. The sender +/// actor can wait for a response. Actors are not referenced directly, +/// but by address [`Addr`](struct.Addr.html) To be able to handle a +/// specific message actor has to provide a +/// [`Handler`](trait.Handler.html) implementation for this +/// message. All messages are statically typed. A message can be +/// handled in asynchronous fashion. An actor can spawn other actors +/// or add futures or streams to the execution context. The actor +/// trait provides several methods that allow controlling the actor +/// lifecycle. +/// +/// # Actor lifecycle +/// +/// ## Started +/// +/// An actor starts in the `Started` state, during this state the +/// `started` method gets called. +/// +/// ## Running +/// +/// After an actor's `started` method got called, the actor +/// transitions to the `Running` state. An actor can stay in the +/// `running` state for an indefinite amount of time. +/// +/// ## Stopping +/// +/// The actor's execution state changes to `stopping` in the following +/// situations: +/// +/// * `Context::stop` gets called by actor itself +/// * all addresses to the actor get dropped +/// * no evented objects are registered in its context. +/// +/// An actor can return from the `stopping` state to the `running` +/// state by creating a new address or adding an evented object, like +/// a future or stream, in its `Actor::stopping` method. +/// +/// If an actor changed to a `stopping` state because +/// `Context::stop()` got called, the context then immediately stops +/// processing incoming messages and calls the `Actor::stopping()` +/// method. If an actor does not return back to a `running` state, +/// all unprocessed messages get dropped. +/// +/// ## Stopped +/// +/// If an actor does not modify execution context while in stopping +/// state, the actor state changes to `Stopped`. This state is +/// considered final and at this point the actor gets dropped. +#[allow(unused_variables)] +pub trait Actor: Sized + Unpin + 'static { + /// Actor execution context type + type Context: ActorContext; + + /// Called when an actor gets polled the first time. + fn started(&mut self, ctx: &mut Self::Context) {} + + /// Called after an actor is in `Actor::Stopping` state. + /// + /// There can be several reasons for stopping: + /// + /// - `Context::stop` gets called by the actor itself. + /// - All addresses to the current actor get dropped and no more + /// evented objects are left in the context. + /// + /// An actor can return from the stopping state to the running + /// state by returning `Running::Continue`. + fn stopping(&mut self, ctx: &mut Self::Context) -> Running { + Running::Stop + } + + /// Called after an actor is stopped. + /// + /// This method can be used to perform any needed cleanup work or + /// to spawn more actors. This is the final state, after this + /// method got called, the actor will be dropped. + fn stopped(&mut self, ctx: &mut Self::Context) {} + + /// Start a new asynchronous actor, returning its address. + /// + /// # Examples + /// + /// ``` + /// use actix::prelude::*; + /// + /// struct MyActor; + /// impl Actor for MyActor { + /// type Context = Context; + /// } + /// + /// #[actix::main] + /// async fn main() { + /// // start actor and get its address + /// let addr = MyActor.start(); + /// # System::current().stop(); + /// } + /// ``` + fn start(self) -> Addr + where + Self: Actor>, + { + Context::new().run(self) + } + + /// Construct and start a new asynchronous actor, returning its + /// address. + /// + /// This is constructs a new actor using the `Default` trait, and + /// invokes its `start` method. + fn start_default() -> Addr + where + Self: Actor> + Default, + { + Self::default().start() + } + + /// Start new actor in arbiter's thread. + fn start_in_arbiter(wrk: &ArbiterHandle, f: F) -> Addr + where + Self: Actor>, + F: FnOnce(&mut Context) -> Self + Send + 'static, + { + let (tx, rx) = channel::channel(DEFAULT_CAPACITY); + + // create actor + wrk.spawn_fn(move || { + let mut ctx = Context::with_receiver(rx); + let act = f(&mut ctx); + let fut = ctx.into_future(act); + + actix_rt::spawn(fut); + }); + + Addr::new(tx) + } + + /// Start a new asynchronous actor given a `Context`. + /// + /// Use this method if you need the `Context` object during actor + /// initialization. + /// + /// # Examples + /// + /// ``` + /// use actix::prelude::*; + /// + /// struct MyActor { + /// val: usize, + /// } + /// impl Actor for MyActor { + /// type Context = Context; + /// } + /// + /// #[actix::main] + /// async fn main() { + /// let addr = MyActor::create(|ctx: &mut Context| MyActor { val: 10 }); + /// # System::current().stop(); + /// } + /// ``` + fn create(f: F) -> Addr + where + Self: Actor>, + F: FnOnce(&mut Context) -> Self, + { + let mut ctx = Context::new(); + let act = f(&mut ctx); + ctx.run(act) + } +} +```` diff --git a/src/rust/diesel/upgrade-diesel-to-2.0.md b/src/rust/diesel/upgrade-diesel-to-2.0.md new file mode 100644 index 0000000..276cf28 --- /dev/null +++ b/src/rust/diesel/upgrade-diesel-to-2.0.md @@ -0,0 +1,346 @@ +# Upgrade diesel to 2.0 + +- [An introduction to diesel 2.0](#an-introduction-to-diesel-20) +- [Upgrade to diesel 2.0](#upgrade-to-diesel-20) + - [Upgrade diesel version in Cargo.toml](#upgrade-diesel-version-in-cargotoml) + - [Add mut to PgConnection and dao functions](#add-mut-to-pgconnection-and-dao-functions) + - [Derive attributes](#derive-attributes) +- [Refs](#refs) + +# An introduction to diesel 2.0 + +Diesel 2.0 has breaking changes compared to 1.4.x. + +Any code base migrating from Diesel 1.4.x to Diesel 2.0 is expected to be affected at least by the following changes: + +- [Diesel now requires a mutable reference to the connection](https://diesel.rs/guides/migration_guide.html#2-0-0-mutable-connection) +- [Changed derive attributes](https://diesel.rs/guides/migration_guide.html#2-0-0-derive-attributes) + +# Upgrade to diesel 2.0 + +## Upgrade diesel version in Cargo.toml + +In order to upgrade diesel to 2.0, we need to change dependencies in `Cargo.toml`. + +```diff +diff --git a/rust/projects/diesel_2.0_example/Cargo.toml b/rust/projects/diesel_2.0_example/Cargo.toml +index 8f5a1aa..5164334 100644 +--- a/rust/projects/diesel_2.0_example/Cargo.toml ++++ b/rust/projects/diesel_2.0_example/Cargo.toml +@@ -5,12 +5,13 @@ authors = ["lichuan "] + edition = "2018" + + [dependencies] +-diesel = { version = "1.4", features = [ ++diesel = { version = "2.1.2", features = [ + "postgres", + "serde_json", + "chrono", + "numeric", + "64-column-tables", ++ "r2d2", + ] } + # r2d2 = "0.8" + # r2d2-diesel = "1.0" + dotenv = "0.14" + # actix-web = "1.0.3" + chrono = { version = "0.4.7", features = ["serde"] } +``` + +We change diesel version to `2.1.2` and remove `r2d2` and `r2d2-diesel` dependencies, as they are included in `diesel` crate, by specifying the `r2d2` feature in `diesel` crate `diesel = { version = "2.1.2", features = [ "r2d2" ] }`. + +## Add mut to PgConnection and dao functions + +Diesel now requires mutable access to the Connection to perform any database interaction. The following changes are required for all usages of any Connection type: + +```diff +- let connection = PgConnection::establish_connection("…")?; +- let result = some_query.load(&connection)?; ++ let mut connection = PgConnection::establish_connection("…")?; ++ let result = some_query.load(&mut connection)?; +``` + +Here are the changes for our own code: + +```diff +diff --git a/rust/projects/diesel_2.0_example/src/bin/contacts.rs b/rust/projects/diesel_2.0_example/src/bin/contacts.rs +index a74e29397..6efc8ef4f 100644 +--- a/rust/projects/diesel_2.0_example/src/bin/contacts.rs ++++ b/rust/projects/diesel_2.0_example/src/bin/contacts.rs +@@ -16,11 +16,15 @@ fn test_contacts() { + env::var("DATABASE_URL").unwrap_or(LOCAL_DATABASE_URL.into()); + let pool = db::init_pool(database_url); + // https://github.com/sfackler/r2d2/issues/37 +- let connection = pool.get().unwrap(); ++ let mut conn = pool.get().unwrap(); + +- let conn: &PgConnection = &connection; +- conn.execute("TRUNCATE TABLE contacts").unwrap(); +- conn.execute("alter sequence contacts_id_seq restart;").unwrap(); ++ diesel::sql_query("TRUNCATE TABLE contacts").execute(&mut conn).unwrap(); ++ diesel::sql_query("alter sequence contacts_id_seq restart;") ++ .execute(&mut conn) ++ .unwrap(); ++ ++ // conn.execute("TRUNCATE TABLE contacts").unwrap(); ++ // conn.execute("alter sequence contacts_id_seq restart;").unwrap(); + + let santas_address: serde_json::Value = serde_json::from_str( + r#"{ +@@ -61,7 +65,7 @@ fn test_contacts() { + }, + ]; + +- let contacts = create_contracts(&conn, &new_contacts).unwrap(); ++ let contacts = create_contracts(&mut conn, &new_contacts).unwrap(); + println!("{:?}", contacts); + + // let inserted_address = insert_into(contacts) +@@ -75,9 +79,7 @@ fn get_contacts() { + let database_url = + env::var("DATABASE_URL").unwrap_or(LOCAL_DATABASE_URL.into()); + let pool = db::init_pool(database_url); +- let connection = pool.get().unwrap(); +- +- let conn: &PgConnection = &connection; ++ let mut conn = pool.get().unwrap(); + + let santas_address: serde_json::Value = serde_json::from_str( + r#"{ +@@ -86,12 +88,13 @@ fn get_contacts() { + ) + .unwrap(); + +- let contacts = get_contacts_by_address(&conn, &santas_address).unwrap(); ++ let contacts = get_contacts_by_address(&mut conn, &santas_address).unwrap(); + println!("{:?}", contacts); + + let santas_address2: serde_json::Value = json!(true); + +- let contacts = get_contacts_by_address(&conn, &santas_address2).unwrap(); ++ let contacts = ++ get_contacts_by_address(&mut conn, &santas_address2).unwrap(); + println!("{:?}", contacts); + } + +diff --git a/rust/projects/diesel_2.0_example/src/bin/select-limit-offset.rs b/rust/projects/diesel_2.0_example/src/bin/select-limit-offset.rs +index a9c583039..a69489b7a 100644 +--- a/rust/projects/diesel_2.0_example/src/bin/select-limit-offset.rs ++++ b/rust/projects/diesel_2.0_example/src/bin/select-limit-offset.rs +@@ -10,13 +10,11 @@ fn select_limit_offset() { + env::var("DATABASE_URL").unwrap_or(local_database_url.into()); + let pool = db::init_pool(database_url); + // https://github.com/sfackler/r2d2/issues/37 +- let connection = pool.get().unwrap(); +- +- let _conn: &PgConnection = &connection; ++ let mut conn = pool.get().unwrap(); + + let limit = 2; + let offset = 2; +- let all = get_select_limit_offset(&connection, limit, offset).unwrap(); ++ let all = get_select_limit_offset(&mut conn, limit, offset).unwrap(); + println!("select : {:?} records", all); + } + +@@ -26,17 +24,15 @@ fn select_limit_offset_loop() { + env::var("DATABASE_URL").unwrap_or(local_database_url.into()); + let pool = db::init_pool(database_url); + // https://github.com/sfackler/r2d2/issues/37 +- let connection = pool.get().unwrap(); +- +- let _conn: &PgConnection = &connection; ++ let mut conn = pool.get().unwrap(); + + let limit = 2; + let mut offset = 0; +- let all = get_select_limit_offset(&connection, limit, offset).unwrap(); ++ let all = get_select_limit_offset(&mut conn, limit, offset).unwrap(); + println!("select : {:?} records", all); + + loop { +- if let Ok(res) = get_select_limit_offset(&connection, limit, offset) { ++ if let Ok(res) = get_select_limit_offset(&mut conn, limit, offset) { + if res.len() == 0 { + break; + } +diff --git a/rust/projects/diesel_2.0_example/src/bin/test-connect-r2d2-pool-actix.rs b/rust/projects/diesel_2.0_example/src/bin/test-connect-r2d2-pool-actix.rs +index 1010a2380..44bce1eb4 100644 +--- a/rust/projects/diesel_2.0_example/src/bin/test-connect-r2d2-pool-actix.rs ++++ b/rust/projects/diesel_2.0_example/src/bin/test-connect-r2d2-pool-actix.rs +@@ -2,15 +2,22 @@ extern crate diesel; + + use std::env; + +-use actix_web::{web, App, HttpRequest, HttpServer, Responder}; ++use actix_web::{web, App, HttpServer, Responder}; + use diesel_example::db; + +-fn greet(req: HttpRequest) -> impl Responder { +- let name = req.match_info().get("name").unwrap_or("World"); +- format!("Hello {}!", &name) ++// TODO: why compile error for actix-web 4? ++// fn greet(req: HttpRequest) -> impl Responder { ++// let name = req.match_info().get("name").unwrap_or("World"); ++// format!("Hello {}!", &name) ++// } ++ ++// #[get("/hello/{name}")] ++async fn greet(name: web::Path) -> impl Responder { ++ format!("Hello {}!", name) + } + +-fn main() { ++#[actix_web::main] ++async fn main() -> std::io::Result<()> { + let database_url = env::var("DATABASE_URL").expect("set DATABASE_URL"); + let pool = db::init_pool(database_url); + // https://github.com/sfackler/r2d2/issues/37 +@@ -26,5 +33,5 @@ fn main() { + .bind("127.0.0.1:8000") + .expect("Can not bind to port 8000") + .run() +- .unwrap(); ++ .await + } +diff --git a/rust/projects/diesel_2.0_example/src/bin/test-partial-inserts.rs b/rust/projects/diesel_2.0_example/src/bin/test-partial-inserts.rs +index 1819a9677..0fa4e2650 100644 +--- a/rust/projects/diesel_2.0_example/src/bin/test-partial-inserts.rs ++++ b/rust/projects/diesel_2.0_example/src/bin/test-partial-inserts.rs +@@ -1,4 +1,3 @@ +- + use diesel_example::dao::partial_inserts::create_partial_inserts; + use diesel_example::db; + use diesel_example::model::partial_inserts::NewPartialInsert; +@@ -10,7 +9,7 @@ fn main() { + env::var("DATABASE_URL").unwrap_or(local_database_url.into()); + let pool = db::init_pool(database_url); + // https://github.com/sfackler/r2d2/issues/37 +- let connection = pool.get().unwrap(); ++ let mut conn = pool.get().unwrap(); + + let v = vec![ + NewPartialInsert { user_id: 5, name: Some("3".to_string()) }, +@@ -20,7 +19,7 @@ fn main() { + // 如果每次插入多个,其中一个报错(比如以上 user_id = 2),整体插入失败(5 不被插入) + // Err(DatabaseError(UniqueViolation, "duplicate key value violates unique constraint \"ui_partial_inserts_user_id\"")) + +- let r = create_partial_inserts(&connection, &v); ++ let r = create_partial_inserts(&mut conn, &v); + + println!("{:?}", r); + } +diff --git a/rust/projects/diesel_2.0_example/src/bin/timestamp-with-zone.rs b/rust/projects/diesel_2.0_example/src/bin/timestamp-with-zone.rs +index e5a793222..1d3b3e72d 100644 +--- a/rust/projects/diesel_2.0_example/src/bin/timestamp-with-zone.rs ++++ b/rust/projects/diesel_2.0_example/src/bin/timestamp-with-zone.rs +@@ -1,7 +1,5 @@ +-use chrono::{DateTime}; +-use diesel_example::dao::timestamp_with_zone::{ +- create_timestamp_with_zones, +-}; ++use chrono::DateTime; ++use diesel_example::dao::timestamp_with_zone::create_timestamp_with_zones; + use diesel_example::db; + use diesel_example::model::timestamp_with_zone::NewTimestampWithZone; + use std::env; +@@ -12,7 +10,7 @@ fn main() { + env::var("DATABASE_URL").unwrap_or(local_database_url.into()); + let pool = db::init_pool(database_url); + // https://github.com/sfackler/r2d2/issues/37 +- let connection = pool.get().unwrap(); ++ let mut conn = pool.get().unwrap(); + + /* + let mut user_id = 123; +@@ -45,7 +43,7 @@ fn main() { + NewTimestampWithZone { user_id: 109, created_at: dt.into() }, + ]; + +- let inserted = create_timestamp_with_zones(&connection, &zones); ++ let inserted = create_timestamp_with_zones(&mut conn, &zones); + match inserted { + Ok(ref v) => { + println!( +diff --git a/rust/projects/diesel_2.0_example/src/bin/updated-at.rs b/rust/projects/diesel_2.0_example/src/bin/updated-at.rs +index c536b6a9c..27c5a37da 100644 +--- a/rust/projects/diesel_2.0_example/src/bin/updated-at.rs ++++ b/rust/projects/diesel_2.0_example/src/bin/updated-at.rs +@@ -10,7 +10,7 @@ fn main() { + env::var("DATABASE_URL").unwrap_or(local_database_url.into()); + let pool = db::init_pool(database_url); + // https://github.com/sfackler/r2d2/issues/37 +- let connection = pool.get().unwrap(); ++ let mut conn = pool.get().unwrap(); + + let user_id = 123; + let n = NewWeiboFeedCrawlStatus { +@@ -20,11 +20,8 @@ fn main() { + created_at: Local::now().naive_local(), + updated_at: None, + }; +- create_weibo_feed_crawl_status(&connection, &n); ++ create_weibo_feed_crawl_status(&mut conn, &n); + update_total_page_count_and_next_page_index_by_user_id( +- user_id, +- &connection, +- 1, +- 1, ++ user_id, &mut conn, 1, 1, + ); + } +diff --git a/rust/projects/diesel_2.0_example/src/bin/upsert-if-condition.rs b/rust/projects/diesel_2.0_example/src/bin/upsert-if-condition.rs +index ca7f19d77..807f74272 100644 +--- a/rust/projects/diesel_2.0_example/src/bin/upsert-if-condition.rs ++++ b/rust/projects/diesel_2.0_example/src/bin/upsert-if-condition.rs +@@ -10,11 +10,15 @@ fn upsert_on_conflict_id() { + env::var("DATABASE_URL").unwrap_or(local_database_url.into()); + let pool = db::init_pool(database_url); + // https://github.com/sfackler/r2d2/issues/37 +- let connection = pool.get().unwrap(); ++ let mut conn = pool.get().unwrap(); + +- let conn: &PgConnection = &connection; +- conn.execute("TRUNCATE TABLE upserts_if_condition").unwrap(); +- conn.execute("alter sequence upserts_if_condition_id_seq restart;") ++ // let conn: &mut PgConnection = &mut connection; ++ // conn.execute("TRUNCATE TABLE upserts_if_condition").unwrap(); ++ // conn.execute("alter sequence upserts_if_condition_id_seq restart;") ++ // .unwrap(); ++ ++ diesel::sql_query("TRUNCATE TABLE upserts_if_condition") ++ .execute(&mut conn) + .unwrap(); + + let u = NewUpsertIfCondition { +@@ -27,7 +31,7 @@ fn upsert_on_conflict_id() { + next: Some(9), + email: Some("e".into()), + }; +- let inserted = create_upserts_if_condition_by_sql(&connection, &u).unwrap(); ++ let inserted = create_upserts_if_condition_by_sql(&mut conn, &u).unwrap(); + println!("{} records inserted", inserted.len()); + } +``` + +## Derive attributes + +You should add `#[diesel()]` when using derive attributes. + +Below is an example of using `table_name` macro to define `NewTimestampWithZone` struct. + +```rust +#[derive(Insertable, Debug)] +// NOTE: For diesel 1.4.x, we use `table_name` macro directly. +// #[table_name = "timestamp_with_zone"] +#[table_name = "timestamp_with_zone"] +pub struct NewTimestampWithZone { + pub user_id: i64, + pub created_at: DateTime, +} +``` + +# Refs + +Diesel 2.0 migration guide +https://diesel.rs/guides/migration_guide.html diff --git a/src/rust/diesel/use-jsonb-in-diesel.md b/src/rust/diesel/use-jsonb-in-diesel.md new file mode 100644 index 0000000..85035dc --- /dev/null +++ b/src/rust/diesel/use-jsonb-in-diesel.md @@ -0,0 +1,1851 @@ +# Diesel JSONB Example + +- [Into](#into) +- [Install dependencies](#install-dependencies) +- [Write sql for migration](#write-sql-for-migration) +- [run migration](#run-migration) +- [Create database models](#create-database-models) +- [Add more dependencies](#add-more-dependencies) +- [Modify main.rs](#modify-mainrs) +- [Build](#build) + - [Build and fail](#build-and-fail) + - [Fix: import `orders` from `schema` module](#fix-import-orders-from-schema-module) + - [Fix: change `total_amount` type from `f64` to `BigDecimal`](#fix-change-total_amount-type-from-f64-to-bigdecimal) + - [Fix: Change `metadata` type from `Value` to `Option`](#fix-change-metadata-type-from-value-to-option) +- [Create orders](#create-orders) +- [Query orders](#query-orders) +- [Update order](#update-order) +- [Delete order](#delete-order) +- [Summary](#summary) +- [Refs](#refs) + +# Into + +This is a simple example of using Diesel with JSONB in a Rust project. + +# Install dependencies + +First, let's create a new project and add dependencies: + +```bash +# Init project +cargo init order-diesel-jsonb-example + +# Add dependencies +cd order-diesel-jsonb-example +cargo add diesel -F postgres +cargo add dotenvy + +# Install diesel cli +cargo install diesel_cli + +# Tell diesel where to find the database +# echo DATABASE_URL=postgres://username:password@localhost/diesel_demo > .env +echo DATABASE_URL=postgres://localhost/diesel_demo > .env + +# Create postgres database +createdb diesel_demo +psql diesel_demo + +# setup diesel and run migrations +diesel setup +diesel migration generate create_orders + +# Output: +# Creating migrations/2024-12-16-120623_create_orders/up.sql +# Creating migrations/2024-12-16-120623_create_orders/down.sql +``` + +# Write sql for migration + +As diesel documents say: + +> Migrations allow us to evolve the database schema over time. Each migration consists of an up.sql file to apply the changes and a down.sql file to revert them. + +diesel will create `migrations` directory after running `diesel migration generate create_orders`. It will create `up.sql` and `down.sql` files. + +Let's write some sql in `migrations/2024-12-16-120623_create_orders/up.sql`: + +```sql +-- Your SQL goes here +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL, + total_amount DECIMAL(10, 2) NOT NULL, + order_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + metadata JSONB +); + +CREATE INDEX idx_order_metadata ON orders USING gin (metadata); +``` + +And `migrations/2024-12-16-120623_create_orders/down.sql`: + +```sql +DROP TABLE orders; +``` + +# run migration + +Run `diesel migration run` will create table in postgres: + +``` +dylan@/tmp:diesel_demo> \d orders; ++--------------+-----------------------------+------------------------------------------------------+ +| Column | Type | Modifiers | +|--------------+-----------------------------+------------------------------------------------------| +| id | integer | not null default nextval('orders_id_seq'::regclass) | +| user_id | integer | not null | +| total_amount | numeric(10,2) | not null | +| order_date | timestamp without time zone | not null default CURRENT_TIMESTAMP | +| metadata | jsonb | | ++--------------+-----------------------------+------------------------------------------------------+ +Indexes: + "orders_pkey" PRIMARY KEY, btree (id) + "idx_order_metadata" gin (metadata) + +Time: 0.034s +``` + +Also, `diesel migration generate create_orders` will generate the following code in `src/schema.rs`: + +``` +// @generated automatically by Diesel CLI. + +diesel::table! { + orders (id) { + id -> Int4, + user_id -> Int4, + total_amount -> Numeric, + order_date -> Timestamp, + metadata -> Nullable, + } +} +``` + +Schema defines a Rust module representing the table structure. + +The key components for the schema are: + +- Table Structure: Each table is represented by a struct, typically referenced as `users::table`. +- Column Definitions: Each column is represented by a struct implementing the `Expression` trait to specify SQL types. +- DSL Module: Provides a convenient syntax for queries, making them less verbose than writing SQL directly. + +By leveraging Diesel's powerful features, you can easily interact with the database, perform complex queries, and manage your data efficiently. + +```rust +use diesel::prelude::*; +use dotenvy::dotenv; +use diesel::pg::PgConnection; +use diesel::r2d2::ConnectionManager; +use diesel::r2d2::Pool; + +use crate::schema::orders; + +pub type PgPool = Pool>; + +pub fn establish_connection() -> PgPool { + dotenv().ok(); + + let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + let manager = ConnectionManager::::new(database_url); + Pool::builder() + .build(manager) + .expect("Failed to create pool.") +} + +pub fn create_order(conn: &PgConnection, new_order: NewOrder) -> Result { + use crate::schema::orders; + + diesel::insert_into(orders::table) + .values(&new_order) + .get_result(conn) +} +``` + +# Create database models + +Now, let's create database models in `src/models.rs`. + +```rust +use diesel::prelude::*; +use serde::{Deserialize, Serialize}; +use chrono::NaiveDateTime; +use serde_json::Value; + +#[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)] +#[diesel(table_name = orders)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct Order { + pub id: i32, + pub user_id: i32, + pub total_amount: f64, + pub order_date: NaiveDateTime, + pub metadata: Value, // This will use JSONB +} + +#[derive(Insertable, Deserialize)] +#[diesel(table_name = orders)] +pub struct NewOrder { + pub user_id: i32, + pub total_amount: f64, + pub metadata: Value, +} +``` + +Here are some notes about the code: + +- `#[derive(Queryable)]` will generate all of the code needed to load a `Order` struct from a SQL query. + +- `#[derive(Selectable)]` will generate code to construct a matching select clause based on your model type based on the table defined via `#[diesel(table_name = orders)]`. + +- `#[diesel(check_for_backend(diesel::pg::Pg))` (or `sqlite::SQLite` or `mysql::MySQL`) adds additional compile time checks to verify that all field types in your struct are compatible with their corresponding SQL side expressions. This part is optional, but it greatly improves the generated compiler error messages. + +If any types are not compatible with column type in database, Diesel will emit a compile-time error. + +# Add more dependencies + +```bash +cargo add anyhow serde_json +cargo add chrono -F serde +cargo add serde -F derive +``` + +Here is the output: + +```bash +> cargo add anyhow serde_json + Updating crates.io index + Adding anyhow v1.0.94 to dependencies + Features: + + std + - backtrace + Adding serde_json v1.0.133 to dependencies + Features: + + std + - alloc + - arbitrary_precision + - float_roundtrip + - indexmap + - preserve_order + - raw_value + - unbounded_depth + Updating crates.io index + Blocking waiting for file lock on package cache + Locking 6 packages to latest compatible versions + Adding anyhow v1.0.94 + Adding memchr v2.7.4 + Adding ryu v1.0.18 + Adding serde v1.0.216 + Adding serde_derive v1.0.216 + Adding serde_json v1.0.133 +> cargo add chrono -F serde + Updating crates.io index + Adding chrono v0.4.39 to dependencies + Features: + + alloc + + android-tzdata + + clock + + iana-time-zone + + js-sys + + now + + oldtime + + serde + + std + + wasm-bindgen + + wasmbind + + winapi + + windows-targets + - __internal_bench + - arbitrary + - libc + - pure-rust-locales + - rkyv + - rkyv-16 + - rkyv-32 + - rkyv-64 + - rkyv-validation + - unstable-locales + Updating crates.io index + Blocking waiting for file lock on package cache + Locking 31 packages to latest compatible versions + Adding android-tzdata v0.1.1 + Adding android_system_properties v0.1.5 + Adding autocfg v1.4.0 + Adding bumpalo v3.16.0 + Adding cc v1.2.4 + Adding cfg-if v1.0.0 + Adding chrono v0.4.39 + Adding core-foundation-sys v0.8.7 + Adding iana-time-zone v0.1.61 + Adding iana-time-zone-haiku v0.1.2 + Adding js-sys v0.3.76 + Adding libc v0.2.168 + Adding log v0.4.22 + Adding num-traits v0.2.19 + Adding once_cell v1.20.2 + Adding shlex v1.3.0 + Adding wasm-bindgen v0.2.99 + Adding wasm-bindgen-backend v0.2.99 + Adding wasm-bindgen-macro v0.2.99 + Adding wasm-bindgen-macro-support v0.2.99 + Adding wasm-bindgen-shared v0.2.99 + Adding windows-core v0.52.0 + Adding windows-targets v0.52.6 + Adding windows_aarch64_gnullvm v0.52.6 + Adding windows_aarch64_msvc v0.52.6 + Adding windows_i686_gnu v0.52.6 + Adding windows_i686_gnullvm v0.52.6 + Adding windows_i686_msvc v0.52.6 + Adding windows_x86_64_gnu v0.52.6 + Adding windows_x86_64_gnullvm v0.52.6 + Adding windows_x86_64_msvc v0.52.6 +> cargo add serde -F derive + Updating crates.io index + Adding serde v1.0.216 to dependencies + Features: + + derive + + serde_derive + + std + - alloc + - rc + - unstable + Blocking waiting for file lock on package cache + Blocking waiting for file lock on package cache +``` + +# Modify main.rs + +Now, let's modify `main.rs` to create a new order and print the result: + +```rust +mod models; +mod schema; +mod db; + +use diesel::pg::PgConnection; +use diesel::Connection; +use dotenvy::dotenv; +use std::env; + +fn establish_connection() -> PgConnection { + dotenv().ok(); + let database_url = env::var("DATABASE_URL") + .expect("DATABASE_URL must be set"); + PgConnection::establish(&database_url) + .expect(&format!("Error connecting to {}", database_url)) +} + +fn main() { + let conn = &mut establish_connection(); + + // Example usage + let new_order = models::NewOrder { + user_id: 1, + total_amount: 99.99, + metadata: serde_json::json!({ + "items": ["book", "pen"], + "shipping_method": "express", + "gift_wrap": true + }), + }; + + match db::create_order(conn, new_order) { + Ok(order) => println!("Created order: {:?}", order), + Err(e) => eprintln!("Error creating order: {}", e), + } +} +``` + +# Build + +## Build and fail + +Now, let's build the project and see the error: + +```bash +> cargo build + Compiling core-foundation-sys v0.8.7 + Compiling itoa v1.0.14 + Compiling memchr v2.7.4 + Compiling pq-sys v0.6.3 + Compiling num-traits v0.2.19 + Compiling byteorder v1.5.0 + Compiling bitflags v2.6.0 + Compiling serde v1.0.216 + Compiling ryu v1.0.18 + Compiling anyhow v1.0.94 + Compiling dotenvy v0.15.7 + Compiling iana-time-zone v0.1.61 + Compiling diesel v2.2.6 + Compiling serde_json v1.0.133 + Compiling chrono v0.4.39 + Compiling order-diesel-jsonb-example v0.1.0 ( +error[E0433]: failed to resolve: use of undeclared crate or module `orders` + --> src/models.rs:7:23 + | +7 | #[diesel(table_name = orders)] + | ^^^^^^ use of undeclared crate or module `orders` + | +help: a struct with a similar name exists + | +7 | #[diesel(table_name = Order)] + | ~~~~~ +help: consider importing this struct through its public re-export + | +1 + use crate::schema::orders::dsl::orders; + | + +error[E0433]: failed to resolve: use of undeclared crate or module `orders` + --> src/models.rs:18:23 + | +18 | #[diesel(table_name = orders)] + | ^^^^^^ use of undeclared crate or module `orders` + | +help: a struct with a similar name exists + | +18 | #[diesel(table_name = Order)] + | ~~~~~ +help: consider importing this struct through its public re-export + | +1 + use crate::schema::orders::dsl::orders; + | + +warning: unused import: `serde_json::json` + --> src/db.rs:3:5 + | +3 | use serde_json::json; + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` on by default + +error[E0277]: the trait bound `&NewOrder: diesel::Insertable` is not satisfied + --> src/db.rs:10:17 + | +10 | .values(&new_order) + | ------ ^^^^^^^^^^ the trait `diesel::Insertable
` is not implemented for `&NewOrder` + | | + | required by a bound introduced by this call + | +note: required by a bound in `IncompleteInsertStatement::::values` + --> /Users/dylan/.cargo/registry/src/index.crates.io-6f17d22bba15001f/diesel-2.2.6/src/query_builder/insert_statement/mod.rs:115:12 + | +113 | pub fn values(self, records: U) -> InsertStatement + | ------ required by a bound in this associated function +114 | where +115 | U: Insertable, + | ^^^^^^^^^^^^^ required by this bound in `IncompleteInsertStatement::::values` + +error[E0277]: the trait bound `(i32, i32, f64, NaiveDateTime, Value): FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not satisfied + --> src/db.rs:11:21 + | +11 | .get_result(conn) + | ---------- ^^^^ the trait `FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not implemented for `(i32, i32, f64, NaiveDateTime, Value)`, which is required by `InsertStatement: LoadQuery<'_, _, _>` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `FromStaticSqlRow`: + `(T0,)` implements `FromStaticSqlRow<(ST0,), __DB>` + `(T1, T0)` implements `FromStaticSqlRow<(ST1, ST0), __DB>` + `(T1, T2, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST0), __DB>` + `(T1, T2, T3, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST0), __DB>` + `(T1, T2, T3, T4, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST0), __DB>` + `(T1, T2, T3, T4, T5, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T7, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST7, ST0), __DB>` + and 24 others +note: required for `models::Order` to implement `diesel::Queryable<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + --> src/models.rs:6:10 + | +6 | #[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)] + | ^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro +... +9 | pub struct Order { + | ^^^^^ + = note: required for `models::Order` to implement `FromSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + = note: required for `(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable)` to implement `load_dsl::private::CompatibleType` + = note: required for `InsertStatement` to implement `LoadQuery<'_, diesel::PgConnection, models::Order>` +note: required by a bound in `get_result` + --> /Users/dylan/.cargo/registry/src/index.crates.io-6f17d22bba15001f/diesel-2.2.6/src/query_dsl/mod.rs:1722:15 + | +1720 | fn get_result<'query, U>(self, conn: &mut Conn) -> QueryResult + | ---------- required by a bound in this associated function +1721 | where +1722 | Self: LoadQuery<'query, Conn, U>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RunQueryDsl::get_result` + = note: this error originates in the derive macro `Queryable` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `(i32, i32, f64, NaiveDateTime, Value): FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not satisfied + --> src/db.rs:17:16 + | +17 | .first(conn) + | ----- ^^^^ the trait `FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not implemented for `(i32, i32, f64, NaiveDateTime, Value)`, which is required by `SelectStatement, query_builder::select_clause::DefaultSelectClause>, query_builder::distinct_clause::NoDistinctClause, query_builder::where_clause::WhereClause>>>, query_builder::order_clause::NoOrderClause, LimitOffsetClause>, NoOffsetClause>>: LoadQuery<'_, _, _>` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `FromStaticSqlRow`: + `(T0,)` implements `FromStaticSqlRow<(ST0,), __DB>` + `(T1, T0)` implements `FromStaticSqlRow<(ST1, ST0), __DB>` + `(T1, T2, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST0), __DB>` + `(T1, T2, T3, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST0), __DB>` + `(T1, T2, T3, T4, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST0), __DB>` + `(T1, T2, T3, T4, T5, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T7, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST7, ST0), __DB>` + and 24 others +note: required for `models::Order` to implement `diesel::Queryable<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + --> src/models.rs:6:10 + | +6 | #[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)] + | ^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro +... +9 | pub struct Order { + | ^^^^^ + = note: required for `models::Order` to implement `FromSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + = note: required for `(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable)` to implement `load_dsl::private::CompatibleType` + = note: required for `SelectStatement, DefaultSelectClause>, NoDistinctClause, ..., ..., ...>` to implement `LoadQuery<'_, diesel::PgConnection, models::Order>` +note: required by a bound in `first` + --> /Users/dylan/.cargo/registry/src/index.crates.io-6f17d22bba15001f/diesel-2.2.6/src/query_dsl/mod.rs:1779:22 + | +1776 | fn first<'query, U>(self, conn: &mut Conn) -> QueryResult + | ----- required by a bound in this associated function +... +1779 | Limit: LoadQuery<'query, Conn, U>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RunQueryDsl::first` + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-0af85acd9e7b4e2f.long-type-11474480310476070344.txt' + = note: consider using `--verbose` to print the full type name to the console + = note: this error originates in the derive macro `Queryable` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `(i32, i32, f64, NaiveDateTime, Value): FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not satisfied + --> src/db.rs:24:15 + | +24 | .load(conn) + | ---- ^^^^ the trait `FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not implemented for `(i32, i32, f64, NaiveDateTime, Value)`, which is required by `SelectStatement, query_builder::select_clause::DefaultSelectClause>, query_builder::distinct_clause::NoDistinctClause, query_builder::where_clause::WhereClause>>>>: LoadQuery<'_, _, _>` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `FromStaticSqlRow`: + `(T0,)` implements `FromStaticSqlRow<(ST0,), __DB>` + `(T1, T0)` implements `FromStaticSqlRow<(ST1, ST0), __DB>` + `(T1, T2, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST0), __DB>` + `(T1, T2, T3, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST0), __DB>` + `(T1, T2, T3, T4, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST0), __DB>` + `(T1, T2, T3, T4, T5, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T7, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST7, ST0), __DB>` + and 24 others +note: required for `models::Order` to implement `diesel::Queryable<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + --> src/models.rs:6:10 + | +6 | #[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)] + | ^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro +... +9 | pub struct Order { + | ^^^^^ + = note: required for `models::Order` to implement `FromSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + = note: required for `(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable)` to implement `load_dsl::private::CompatibleType` + = note: required for `SelectStatement, DefaultSelectClause>, NoDistinctClause, WhereClause<...>>` to implement `LoadQuery<'_, diesel::PgConnection, models::Order>` +note: required by a bound in `diesel::RunQueryDsl::load` + --> /Users/dylan/.cargo/registry/src/index.crates.io-6f17d22bba15001f/diesel-2.2.6/src/query_dsl/mod.rs:1542:15 + | +1540 | fn load<'query, U>(self, conn: &mut Conn) -> QueryResult> + | ---- required by a bound in this associated function +1541 | where +1542 | Self: LoadQuery<'query, Conn, U>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RunQueryDsl::load` + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-0af85acd9e7b4e2f.long-type-14102635225686123243.txt' + = note: consider using `--verbose` to print the full type name to the console + = note: this error originates in the derive macro `Queryable` (in Nightly builds, run with -Z macro-backtrace for more info) + +Some errors have detailed explanations: E0277, E0433. +For more information about an error, try `rustc --explain E0277`. +warning: `order-diesel-jsonb-example` (bin "order-diesel-jsonb-example") generated 1 warning +error: could not compile `order-diesel-jsonb-example` (bin "order-diesel-jsonb-example") due to 6 previous errors; 1 warning emitted +``` + +## Fix: import `orders` from `schema` module + +Although there are a lot of errors, the rust compiler is able to find the error and give us some hints. + +```bash +error[E0433]: failed to resolve: use of undeclared crate or module `orders` + --> src/models.rs:7:23 + | +7 | #[diesel(table_name = orders)] + | ^^^^^^ use of undeclared crate or module `orders` + | +help: a struct with a similar name exists + | +7 | #[diesel(table_name = Order)] + | ~~~~~ +help: consider importing this struct through its public re-export + | +1 + use crate::schema::orders::dsl::orders; +``` + +Howerver, the hint is not correcty, We should import `orders` from `schema` module(`use crate::schema::orders`), not `use crate::schema::orders::dsl::orders`. + +Let's import `orders` from `schema` module: + +```rust +use crate::schema::orders; +``` + +Or follow the IDE: + +![Import orders](https://github.com/dylankyc/dylankyc.github.io/blob/main/.img/import-orders.png?raw=true) + +![Import orders](https://github.com/dylankyc/dylankyc.github.io/blob/main/.img/import-orders-from-schema.png?raw=true) + +Also, we should add `chrono` and `serde_json` features for `diesel` dependency: + +```bash +cargo add diesel -F chrono,serde_json + Updating crates.io index + Adding diesel v2.2.6 to dependencies + Features: + + 32-column-tables + + chrono + + postgres + + postgres_backend + + serde_json + + with-deprecated + - 128-column-tables + - 64-column-tables + - __with_asan_tests + - extras + - huge-tables + - i-implement-a-third-party-backend-and-opt-into-breaking-changes + - ipnet-address + - large-tables + - mysql + - mysql_backend + - mysqlclient-src + - network-address + - numeric + - pq-src + - quickcheck + - r2d2 + - returning_clauses_for_sqlite_3_35 + - sqlite + - time + - unstable + - uuid + - without-deprecated +``` + +Let's build it again. + +```bash +> cargo build + Compiling diesel v2.2.6 + Compiling order-diesel-jsonb-example v0.1.0 +warning: unused import: `serde_json::json` + --> src/db.rs:3:5 + | +3 | use serde_json::json; + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` on by default + +error[E0277]: the trait bound `Value: FromSqlRow, Pg>` is not satisfied + --> src/models.rs:16:19 + | +16 | pub metadata: Value, // This will use JSONB + | ^^^^^ the trait `FromSql, Pg>` is not implemented for `Value`, which is required by `Value: FromSqlRow, Pg>` + | + = note: double check your type mappings via the documentation of `diesel::sql_types::Nullable` + = note: `diesel::sql_query` requires the loading target to column names for loading values. + You need to provide a type that explicitly derives `diesel::deserialize::QueryableByName` + = help: the following other types implement trait `FromSql`: + `Value` implements `FromSql` + `Value` implements `FromSql` + = note: required for `Value` to implement `diesel::Queryable, Pg>` + = note: required for `Value` to implement `FromSqlRow, Pg>` + = help: see issue #48214 + +error[E0277]: the trait bound `f64: FromSqlRow` is not satisfied + --> src/models.rs:14:23 + | +14 | pub total_amount: f64, + | ^^^ the trait `FromSql` is not implemented for `f64`, which is required by `f64: FromSqlRow` + | + = note: double check your type mappings via the documentation of `diesel::sql_types::Numeric` + = note: `diesel::sql_query` requires the loading target to column names for loading values. + You need to provide a type that explicitly derives `diesel::deserialize::QueryableByName` + = help: the trait `FromSql` is implemented for `f64` + = help: for that trait implementation, expected `Double`, found `diesel::sql_types::Numeric` + = note: required for `f64` to implement `diesel::Queryable` + = note: required for `f64` to implement `FromSqlRow` + = help: see issue #48214 + +error[E0277]: the trait bound `f64: diesel::Expression` is not satisfied + --> src/models.rs:8:33 + | +8 | #[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)] + | ^^^^^^^^^^ the trait `diesel::Expression` is not implemented for `f64`, which is required by `f64: AsExpression` + | + = help: the following other types implement trait `diesel::Expression`: + &'a T + (T0, T1) + (T0, T1, T2) + (T0, T1, T2, T3) + (T0, T1, T2, T3, T4) + (T0, T1, T2, T3, T4, T5) + (T0, T1, T2, T3, T4, T5, T6) + (T0, T1, T2, T3, T4, T5, T6, T7) + and 137 others + = note: required for `f64` to implement `AsExpression` + = note: this error originates in the derive macro `Insertable` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `f64: diesel::Expression` is not satisfied + --> src/models.rs:8:33 + | +8 | #[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)] + | ^^^^^^^^^^ the trait `diesel::Expression` is not implemented for `f64`, which is required by `&'insert f64: AsExpression` + | + = help: the following other types implement trait `diesel::Expression`: + &'a T + (T0, T1) + (T0, T1, T2) + (T0, T1, T2, T3) + (T0, T1, T2, T3, T4) + (T0, T1, T2, T3, T4, T5) + (T0, T1, T2, T3, T4, T5, T6) + (T0, T1, T2, T3, T4, T5, T6, T7) + and 137 others + = note: required for `&'insert f64` to implement `diesel::Expression` + = note: required for `&'insert f64` to implement `AsExpression` + = note: this error originates in the derive macro `Insertable` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `f64: diesel::Expression` is not satisfied + --> src/models.rs:19:10 + | +19 | #[derive(Insertable, Deserialize)] + | ^^^^^^^^^^ the trait `diesel::Expression` is not implemented for `f64`, which is required by `f64: AsExpression` + | + = help: the following other types implement trait `diesel::Expression`: + &'a T + (T0, T1) + (T0, T1, T2) + (T0, T1, T2, T3) + (T0, T1, T2, T3, T4) + (T0, T1, T2, T3, T4, T5) + (T0, T1, T2, T3, T4, T5, T6) + (T0, T1, T2, T3, T4, T5, T6, T7) + and 137 others + = note: required for `f64` to implement `AsExpression` + = note: this error originates in the derive macro `Insertable` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `f64: diesel::Expression` is not satisfied + --> src/models.rs:19:10 + | +19 | #[derive(Insertable, Deserialize)] + | ^^^^^^^^^^ the trait `diesel::Expression` is not implemented for `f64`, which is required by `&'insert f64: AsExpression` + | + = help: the following other types implement trait `diesel::Expression`: + &'a T + (T0, T1) + (T0, T1, T2) + (T0, T1, T2, T3) + (T0, T1, T2, T3, T4) + (T0, T1, T2, T3, T4, T5) + (T0, T1, T2, T3, T4, T5, T6) + (T0, T1, T2, T3, T4, T5, T6, T7) + and 137 others + = note: required for `&'insert f64` to implement `diesel::Expression` + = note: required for `&'insert f64` to implement `AsExpression` + = note: this error originates in the derive macro `Insertable` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `f64: diesel::Expression` is not satisfied + --> src/models.rs:14:9 + | +14 | pub total_amount: f64, + | ^^^^^^^^^^^^ the trait `diesel::Expression` is not implemented for `f64`, which is required by `f64: AsExpression` + | + = help: the following other types implement trait `diesel::Expression`: + &'a T + (T0, T1) + (T0, T1, T2) + (T0, T1, T2, T3) + (T0, T1, T2, T3, T4) + (T0, T1, T2, T3, T4, T5) + (T0, T1, T2, T3, T4, T5, T6) + (T0, T1, T2, T3, T4, T5, T6, T7) + and 137 others + = note: required for `f64` to implement `AsExpression` + +error[E0277]: the trait bound `f64: diesel::Expression` is not satisfied + --> src/models.rs:14:9 + | +14 | pub total_amount: f64, + | ^^^^^^^^^^^^ the trait `diesel::Expression` is not implemented for `f64`, which is required by `&'insert f64: AsExpression` + | + = help: the following other types implement trait `diesel::Expression`: + &'a T + (T0, T1) + (T0, T1, T2) + (T0, T1, T2, T3) + (T0, T1, T2, T3, T4) + (T0, T1, T2, T3, T4, T5) + (T0, T1, T2, T3, T4, T5, T6) + (T0, T1, T2, T3, T4, T5, T6, T7) + and 137 others + = note: required for `&'insert f64` to implement `diesel::Expression` + = note: required for `&'insert f64` to implement `AsExpression` + +error[E0277]: the trait bound `f64: diesel::Expression` is not satisfied + --> src/models.rs:23:9 + | +23 | pub total_amount: f64, + | ^^^^^^^^^^^^ the trait `diesel::Expression` is not implemented for `f64`, which is required by `f64: AsExpression` + | + = help: the following other types implement trait `diesel::Expression`: + &'a T + (T0, T1) + (T0, T1, T2) + (T0, T1, T2, T3) + (T0, T1, T2, T3, T4) + (T0, T1, T2, T3, T4, T5) + (T0, T1, T2, T3, T4, T5, T6) + (T0, T1, T2, T3, T4, T5, T6, T7) + and 137 others + = note: required for `f64` to implement `AsExpression` + +error[E0277]: the trait bound `f64: diesel::Expression` is not satisfied + --> src/models.rs:23:9 + | +23 | pub total_amount: f64, + | ^^^^^^^^^^^^ the trait `diesel::Expression` is not implemented for `f64`, which is required by `&'insert f64: AsExpression` + | + = help: the following other types implement trait `diesel::Expression`: + &'a T + (T0, T1) + (T0, T1, T2) + (T0, T1, T2, T3) + (T0, T1, T2, T3, T4) + (T0, T1, T2, T3, T4, T5) + (T0, T1, T2, T3, T4, T5, T6) + (T0, T1, T2, T3, T4, T5, T6, T7) + and 137 others + = note: required for `&'insert f64` to implement `diesel::Expression` + = note: required for `&'insert f64` to implement `AsExpression` + +error[E0277]: the trait bound `f64: diesel::Expression` is not satisfied + --> src/db.rs:10:10 + | +10 | .values(&new_order) + | ^^^^^^ the trait `diesel::Expression` is not implemented for `f64`, which is required by `&f64: AsExpression` + | + = help: the following other types implement trait `diesel::Expression`: + &'a T + (T0, T1) + (T0, T1, T2) + (T0, T1, T2, T3) + (T0, T1, T2, T3, T4) + (T0, T1, T2, T3, T4, T5) + (T0, T1, T2, T3, T4, T5, T6) + (T0, T1, T2, T3, T4, T5, T6, T7) + and 137 others + = note: required for `&f64` to implement `diesel::Expression` + = note: required for `&f64` to implement `AsExpression` + +error[E0277]: the trait bound `f64: AppearsOnTable` is not satisfied + --> src/db.rs:11:21 + | +11 | .get_result(conn) + | ---------- ^^^^ the trait `AppearsOnTable` is not implemented for `f64`, which is required by `InsertStatement>>, DefaultableColumnInsertValue>, DefaultableColumnInsertValue, &Value>>>), table>>: LoadQuery<'_, _, _>` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `AppearsOnTable`: + `&'a T` implements `AppearsOnTable` + `(T0, T1)` implements `AppearsOnTable` + `(T0, T1, T2)` implements `AppearsOnTable` + `(T0, T1, T2, T3)` implements `AppearsOnTable` + `(T0, T1, T2, T3, T4)` implements `AppearsOnTable` + `(T0, T1, T2, T3, T4, T5)` implements `AppearsOnTable` + `(T0, T1, T2, T3, T4, T5, T6)` implements `AppearsOnTable` + `(T0, T1, T2, T3, T4, T5, T6, T7)` implements `AppearsOnTable` + and 137 others + = note: required for `&f64` to implement `AppearsOnTable` + = note: required for `DefaultableColumnInsertValue>` to implement `InsertValues<_, table>` + = note: 1 redundant requirement hidden + = note: required for `(DefaultableColumnInsertValue>>, ..., ...)` to implement `InsertValues<_, table>` + = note: required for `ValuesClause<(DefaultableColumnInsertValue>>, ..., ...), ...>` to implement `QueryFragment<_>` + = note: 1 redundant requirement hidden + = note: required for `InsertStatement, ..., ...), ...>, ..., ...>` to implement `QueryFragment<_>` + = note: required for `InsertStatement>, ..., ...), ...>>` to implement `LoadQuery<'_, _, _>` +note: required by a bound in `get_result` + --> /Users/dylan/.cargo/registry/src/index.crates.io-6f17d22bba15001f/diesel-2.2.6/src/query_dsl/mod.rs:1722:15 + | +1720 | fn get_result<'query, U>(self, conn: &mut Conn) -> QueryResult + | ---------- required by a bound in this associated function +1721 | where +1722 | Self: LoadQuery<'query, Conn, U>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RunQueryDsl::get_result` + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-f4ea1891b9fe6645.long-type-12046924592425831098.txt' + = note: consider using `--verbose` to print the full type name to the console + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-f4ea1891b9fe6645.long-type-16463581489430962252.txt' + = note: consider using `--verbose` to print the full type name to the console + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-f4ea1891b9fe6645.long-type-8702231718444061151.txt' + = note: consider using `--verbose` to print the full type name to the console + +error[E0271]: type mismatch resolving `::InsertWithDefaultKeyword == NotSpecialized` + --> src/db.rs:11:21 + | +11 | .get_result(conn) + | ---------- ^^^^ expected `NotSpecialized`, found `IsoSqlDefaultKeyword` + | | + | required by a bound introduced by this call + | + = note: required for `DefaultableColumnInsertValue>` to implement `QueryFragment` + = note: required for `DefaultableColumnInsertValue>` to implement `InsertValues` + = note: 3 redundant requirements hidden + = note: required for `InsertStatement, ..., ...), ...>, ..., ...>` to implement `QueryFragment` + = note: required for `InsertStatement>, ..., ...), ...>>` to implement `LoadQuery<'_, diesel::PgConnection, _>` +note: required by a bound in `get_result` + --> /Users/dylan/.cargo/registry/src/index.crates.io-6f17d22bba15001f/diesel-2.2.6/src/query_dsl/mod.rs:1722:15 + | +1720 | fn get_result<'query, U>(self, conn: &mut Conn) -> QueryResult + | ---------- required by a bound in this associated function +1721 | where +1722 | Self: LoadQuery<'query, Conn, U>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RunQueryDsl::get_result` + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-f4ea1891b9fe6645.long-type-12046924592425831098.txt' + = note: consider using `--verbose` to print the full type name to the console + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-f4ea1891b9fe6645.long-type-9860913280699406432.txt' + = note: consider using `--verbose` to print the full type name to the console + +error[E0277]: the trait bound `(i32, i32, f64, NaiveDateTime, Value): FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not satisfied + --> src/db.rs:11:21 + | +11 | .get_result(conn) + | ---------- ^^^^ the trait `FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not implemented for `(i32, i32, f64, NaiveDateTime, Value)`, which is required by `InsertStatement>>, DefaultableColumnInsertValue>, DefaultableColumnInsertValue, &Value>>>), table>>: LoadQuery<'_, _, _>` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `FromStaticSqlRow`: + `(T0,)` implements `FromStaticSqlRow<(ST0,), __DB>` + `(T1, T0)` implements `FromStaticSqlRow<(ST1, ST0), __DB>` + `(T1, T2, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST0), __DB>` + `(T1, T2, T3, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST0), __DB>` + `(T1, T2, T3, T4, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST0), __DB>` + `(T1, T2, T3, T4, T5, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T7, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST7, ST0), __DB>` + and 24 others +note: required for `models::Order` to implement `diesel::Queryable<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + --> src/models.rs:8:10 + | +8 | #[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)] + | ^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro +... +11 | pub struct Order { + | ^^^^^ + = note: required for `models::Order` to implement `FromSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + = note: required for `(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable)` to implement `load_dsl::private::CompatibleType` + = note: required for `InsertStatement>, ..., ...), ...>>` to implement `LoadQuery<'_, diesel::PgConnection, models::Order>` +note: required by a bound in `get_result` + --> /Users/dylan/.cargo/registry/src/index.crates.io-6f17d22bba15001f/diesel-2.2.6/src/query_dsl/mod.rs:1722:15 + | +1720 | fn get_result<'query, U>(self, conn: &mut Conn) -> QueryResult + | ---------- required by a bound in this associated function +1721 | where +1722 | Self: LoadQuery<'query, Conn, U>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RunQueryDsl::get_result` + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-f4ea1891b9fe6645.long-type-12046924592425831098.txt' + = note: consider using `--verbose` to print the full type name to the console + = note: this error originates in the derive macro `Queryable` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `(i32, i32, f64, NaiveDateTime, Value): FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not satisfied + --> src/db.rs:17:16 + | +17 | .first(conn) + | ----- ^^^^ the trait `FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not implemented for `(i32, i32, f64, NaiveDateTime, Value)`, which is required by `SelectStatement, query_builder::select_clause::DefaultSelectClause>, query_builder::distinct_clause::NoDistinctClause, query_builder::where_clause::WhereClause>>>, query_builder::order_clause::NoOrderClause, LimitOffsetClause>, NoOffsetClause>>: LoadQuery<'_, _, _>` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `FromStaticSqlRow`: + `(T0,)` implements `FromStaticSqlRow<(ST0,), __DB>` + `(T1, T0)` implements `FromStaticSqlRow<(ST1, ST0), __DB>` + `(T1, T2, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST0), __DB>` + `(T1, T2, T3, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST0), __DB>` + `(T1, T2, T3, T4, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST0), __DB>` + `(T1, T2, T3, T4, T5, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T7, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST7, ST0), __DB>` + and 24 others +note: required for `models::Order` to implement `diesel::Queryable<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + --> src/models.rs:8:10 + | +8 | #[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)] + | ^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro +... +11 | pub struct Order { + | ^^^^^ + = note: required for `models::Order` to implement `FromSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + = note: required for `(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable)` to implement `load_dsl::private::CompatibleType` + = note: required for `SelectStatement, DefaultSelectClause>, NoDistinctClause, ..., ..., ...>` to implement `LoadQuery<'_, diesel::PgConnection, models::Order>` +note: required by a bound in `first` + --> /Users/dylan/.cargo/registry/src/index.crates.io-6f17d22bba15001f/diesel-2.2.6/src/query_dsl/mod.rs:1779:22 + | +1776 | fn first<'query, U>(self, conn: &mut Conn) -> QueryResult + | ----- required by a bound in this associated function +... +1779 | Limit: LoadQuery<'query, Conn, U>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RunQueryDsl::first` + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-f4ea1891b9fe6645.long-type-13104014775257459898.txt' + = note: consider using `--verbose` to print the full type name to the console + = note: this error originates in the derive macro `Queryable` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `(i32, i32, f64, NaiveDateTime, Value): FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not satisfied + --> src/db.rs:24:15 + | +24 | .load(conn) + | ---- ^^^^ the trait `FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not implemented for `(i32, i32, f64, NaiveDateTime, Value)`, which is required by `SelectStatement, query_builder::select_clause::DefaultSelectClause>, query_builder::distinct_clause::NoDistinctClause, query_builder::where_clause::WhereClause>>>>: LoadQuery<'_, _, _>` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `FromStaticSqlRow`: + `(T0,)` implements `FromStaticSqlRow<(ST0,), __DB>` + `(T1, T0)` implements `FromStaticSqlRow<(ST1, ST0), __DB>` + `(T1, T2, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST0), __DB>` + `(T1, T2, T3, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST0), __DB>` + `(T1, T2, T3, T4, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST0), __DB>` + `(T1, T2, T3, T4, T5, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T7, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST7, ST0), __DB>` + and 24 others +note: required for `models::Order` to implement `diesel::Queryable<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + --> src/models.rs:8:10 + | +8 | #[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)] + | ^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro +... +11 | pub struct Order { + | ^^^^^ + = note: required for `models::Order` to implement `FromSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + = note: required for `(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable)` to implement `load_dsl::private::CompatibleType` + = note: required for `SelectStatement, DefaultSelectClause>, NoDistinctClause, WhereClause<...>>` to implement `LoadQuery<'_, diesel::PgConnection, models::Order>` +note: required by a bound in `diesel::RunQueryDsl::load` + --> /Users/dylan/.cargo/registry/src/index.crates.io-6f17d22bba15001f/diesel-2.2.6/src/query_dsl/mod.rs:1542:15 + | +1540 | fn load<'query, U>(self, conn: &mut Conn) -> QueryResult> + | ---- required by a bound in this associated function +1541 | where +1542 | Self: LoadQuery<'query, Conn, U>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RunQueryDsl::load` + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-f4ea1891b9fe6645.long-type-451606845660155404.txt' + = note: consider using `--verbose` to print the full type name to the console + = note: this error originates in the derive macro `Queryable` (in Nightly builds, run with -Z macro-backtrace for more info) + +Some errors have detailed explanations: E0271, E0277. +For more information about an error, try `rustc --explain E0271`. +warning: `order-diesel-jsonb-example` (bin "order-diesel-jsonb-example") generated 1 warning +error: could not compile `order-diesel-jsonb-example` (bin "order-diesel-jsonb-example") due to 24 previous errors; 1 warning emitted +``` + +## Fix: change `total_amount` type from `f64` to `BigDecimal` + +That's a lot of errors, let's fix it step by step. + +First, let's see the error for `total_amount` field + +```bash +error[E0277]: the trait bound `f64: FromSqlRow` is not satisfied + --> src/models.rs:14:23 + | +14 | pub total_amount: f64, + | ^^^ the trait `FromSql` is not implemented for `f64`, which is required by `f64: FromSqlRow` + | + = note: double check your type mappings via the documentation of `diesel::sql_types::Numeric` + = note: `diesel::sql_query` requires the loading target to column names for loading values. + You need to provide a type that explicitly derives `diesel::deserialize::QueryableByName` + = help: the trait `FromSql` is implemented for `f64` + = help: for that trait implementation, expected `Double`, found `diesel::sql_types::Numeric` + = note: required for `f64` to implement `diesel::Queryable` + = note: required for `f64` to implement `FromSqlRow` + = help: see issue #48214 + +error[E0277]: the trait bound `f64: diesel::Expression` is not satisfied + --> src/models.rs:8:33 + | +8 | #[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)] + | ^^^^^^^^^^ the trait `diesel::Expression` is not implemented for `f64`, which is required by `f64: AsExpression` + | + = help: the following other types implement trait `diesel::Expression`: + &'a T + (T0, T1) + (T0, T1, T2) + (T0, T1, T2, T3) + (T0, T1, T2, T3, T4) + (T0, T1, T2, T3, T4, T5) + (T0, T1, T2, T3, T4, T5, T6) + (T0, T1, T2, T3, T4, T5, T6, T7) + and 137 others + = note: required for `f64` to implement `AsExpression` + = note: this error originates in the derive macro `Insertable` (in Nightly builds, run with -Z macro-backtrace for more info) +``` + +Notice, in migration file, `total_amount` column type is `Decimal`, and diesel will use `Numeric` type in it's SQL query. + +Here is the source code of `diesel::sql_types::Numeric` + +```rust +/// The arbitrary precision numeric SQL type. +/// +/// This type is only supported on PostgreSQL and MySQL. +/// On SQLite, [`Double`] should be used instead. +/// +/// ### [`ToSql`](crate::serialize::ToSql) impls +/// +/// - [`bigdecimal::BigDecimal`] with `feature = ["numeric"]` +/// +/// ### [`FromSql`](crate::deserialize::FromSql) impls +/// +/// - [`bigdecimal::BigDecimal`] with `feature = ["numeric"]` +/// +/// [`bigdecimal::BigDecimal`]: /bigdecimal/struct.BigDecimal.html +#[derive(Debug, Clone, Copy, Default, QueryId, SqlType)] +#[diesel(postgres_type(oid = 1700, array_oid = 1231))] +#[diesel(mysql_type(name = "Numeric"))] +#[diesel(sqlite_type(name = "Double"))] +pub struct Numeric; +``` + +We should tell diesel to use `numeric` type. + +```sql +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL, + total_amount DECIMAL(10, 2) NOT NULL, -- 👈👈👈👈👈👈 🙋 + order_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + metadata JSONB +); +``` + +Let's enable `numeric` feature to `diesel` dependency to correctly deserialize `total_amount` field. + +```bash +cargo add diesel -F numeric + Updating crates.io index + Adding diesel v2.2.6 to dependencies + Features: + + 32-column-tables + + chrono + + numeric + + postgres + + postgres_backend + + serde_json + + with-deprecated + - 128-column-tables + - 64-column-tables + - __with_asan_tests + - extras + - huge-tables + - i-implement-a-third-party-backend-and-opt-into-breaking-changes + - ipnet-address + - large-tables + - mysql + - mysql_backend + - mysqlclient-src + - network-address + - pq-src + - quickcheck + - r2d2 + - returning_clauses_for_sqlite_3_35 + - sqlite + - time + - unstable + - uuid + - without-deprecated + Locking 4 packages to latest compatible versions + Adding bigdecimal v0.4.7 + Adding libm v0.2.11 + Adding num-bigint v0.4.6 + Adding num-integer v0.1.46 +``` + +Now, let's build it again. + +Still lots of errors: + +```bash + Compiling order-diesel-jsonb-example v0.1.0 +warning: unused import: `serde_json::json` + --> src/db.rs:3:5 + | +3 | use serde_json::json; + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` on by default + +error[E0277]: the trait bound `Value: FromSqlRow, Pg>` is not satisfied + --> src/models.rs:18:19 + | +18 | pub metadata: Value, // This will use JSONB + | ^^^^^ the trait `FromSql, Pg>` is not implemented for `Value`, which is required by `Value: FromSqlRow, Pg>` + | + = note: double check your type mappings via the documentation of `diesel::sql_types::Nullable` + = note: `diesel::sql_query` requires the loading target to column names for loading values. + You need to provide a type that explicitly derives `diesel::deserialize::QueryableByName` + = help: the following other types implement trait `FromSql`: + `Value` implements `FromSql` + `Value` implements `FromSql` + = note: required for `Value` to implement `diesel::Queryable, Pg>` + = note: required for `Value` to implement `FromSqlRow, Pg>` + = help: see issue #48214 + +error[E0277]: the trait bound `(i32, i32, BigDecimal, NaiveDateTime, Value): FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not satisfied + --> src/db.rs:11:21 + | +11 | .get_result(conn) + | ---------- ^^^^ the trait `FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not implemented for `(i32, i32, BigDecimal, NaiveDateTime, Value)`, which is required by `InsertStatement>>, DefaultableColumnInsertValue>>, DefaultableColumnInsertValue, &Value>>>), table>>: LoadQuery<'_, _, _>` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `FromStaticSqlRow`: + `(T0,)` implements `FromStaticSqlRow<(ST0,), __DB>` + `(T1, T0)` implements `FromStaticSqlRow<(ST1, ST0), __DB>` + `(T1, T2, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST0), __DB>` + `(T1, T2, T3, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST0), __DB>` + `(T1, T2, T3, T4, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST0), __DB>` + `(T1, T2, T3, T4, T5, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T7, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST7, ST0), __DB>` + and 24 others +note: required for `models::Order` to implement `diesel::Queryable<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + --> src/models.rs:9:10 + | +9 | #[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)] + | ^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro +... +12 | pub struct Order { + | ^^^^^ + = note: required for `models::Order` to implement `FromSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + = note: required for `(Integer, Integer, Numeric, Timestamp, Nullable)` to implement `load_dsl::private::CompatibleType` + = note: required for `InsertStatement>` to implement `LoadQuery<'_, diesel::PgConnection, models::Order>` +note: required by a bound in `get_result` + --> /Users/dylan/.cargo/registry/src/index.crates.io-6f17d22bba15001f/diesel-2.2.6/src/query_dsl/mod.rs:1722:15 + | +1720 | fn get_result<'query, U>(self, conn: &mut Conn) -> QueryResult + | ---------- required by a bound in this associated function +1721 | where +1722 | Self: LoadQuery<'query, Conn, U>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RunQueryDsl::get_result` + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-cdb3b656aa15b263.long-type-9790004947608049719.txt' + = note: consider using `--verbose` to print the full type name to the console + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-cdb3b656aa15b263.long-type-2452195376562117312.txt' + = note: consider using `--verbose` to print the full type name to the console + = note: this error originates in the derive macro `Queryable` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `(i32, i32, BigDecimal, NaiveDateTime, Value): FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not satisfied + --> src/db.rs:17:16 + | +17 | .first(conn) + | ----- ^^^^ the trait `FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not implemented for `(i32, i32, BigDecimal, NaiveDateTime, Value)`, which is required by `SelectStatement, query_builder::select_clause::DefaultSelectClause>, query_builder::distinct_clause::NoDistinctClause, query_builder::where_clause::WhereClause>>>, query_builder::order_clause::NoOrderClause, LimitOffsetClause>, NoOffsetClause>>: LoadQuery<'_, _, _>` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `FromStaticSqlRow`: + `(T0,)` implements `FromStaticSqlRow<(ST0,), __DB>` + `(T1, T0)` implements `FromStaticSqlRow<(ST1, ST0), __DB>` + `(T1, T2, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST0), __DB>` + `(T1, T2, T3, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST0), __DB>` + `(T1, T2, T3, T4, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST0), __DB>` + `(T1, T2, T3, T4, T5, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T7, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST7, ST0), __DB>` + and 24 others +note: required for `models::Order` to implement `diesel::Queryable<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + --> src/models.rs:9:10 + | +9 | #[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)] + | ^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro +... +12 | pub struct Order { + | ^^^^^ + = note: required for `models::Order` to implement `FromSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + = note: required for `(Integer, Integer, Numeric, Timestamp, Nullable)` to implement `load_dsl::private::CompatibleType` + = note: required for `SelectStatement, DefaultSelectClause<...>, ..., ..., ..., ...>` to implement `LoadQuery<'_, diesel::PgConnection, models::Order>` +note: required by a bound in `first` + --> /Users/dylan/.cargo/registry/src/index.crates.io-6f17d22bba15001f/diesel-2.2.6/src/query_dsl/mod.rs:1779:22 + | +1776 | fn first<'query, U>(self, conn: &mut Conn) -> QueryResult + | ----- required by a bound in this associated function +... +1779 | Limit: LoadQuery<'query, Conn, U>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RunQueryDsl::first` + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-cdb3b656aa15b263.long-type-12865645728958808655.txt' + = note: consider using `--verbose` to print the full type name to the console + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-cdb3b656aa15b263.long-type-2452195376562117312.txt' + = note: consider using `--verbose` to print the full type name to the console + = note: this error originates in the derive macro `Queryable` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `(i32, i32, BigDecimal, NaiveDateTime, Value): FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not satisfied + --> src/db.rs:24:15 + | +24 | .load(conn) + | ---- ^^^^ the trait `FromStaticSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` is not implemented for `(i32, i32, BigDecimal, NaiveDateTime, Value)`, which is required by `SelectStatement, query_builder::select_clause::DefaultSelectClause>, query_builder::distinct_clause::NoDistinctClause, query_builder::where_clause::WhereClause>>>>: LoadQuery<'_, _, _>` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `FromStaticSqlRow`: + `(T0,)` implements `FromStaticSqlRow<(ST0,), __DB>` + `(T1, T0)` implements `FromStaticSqlRow<(ST1, ST0), __DB>` + `(T1, T2, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST0), __DB>` + `(T1, T2, T3, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST0), __DB>` + `(T1, T2, T3, T4, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST0), __DB>` + `(T1, T2, T3, T4, T5, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST0), __DB>` + `(T1, T2, T3, T4, T5, T6, T7, T0)` implements `FromStaticSqlRow<(ST1, ST2, ST3, ST4, ST5, ST6, ST7, ST0), __DB>` + and 24 others +note: required for `models::Order` to implement `diesel::Queryable<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + --> src/models.rs:9:10 + | +9 | #[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)] + | ^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro +... +12 | pub struct Order { + | ^^^^^ + = note: required for `models::Order` to implement `FromSqlRow<(Integer, Integer, diesel::sql_types::Numeric, diesel::sql_types::Timestamp, diesel::sql_types::Nullable), Pg>` + = note: required for `(Integer, Integer, Numeric, Timestamp, Nullable)` to implement `load_dsl::private::CompatibleType` + = note: required for `SelectStatement, DefaultSelectClause>, ..., ...>` to implement `LoadQuery<'_, diesel::PgConnection, models::Order>` +note: required by a bound in `diesel::RunQueryDsl::load` + --> /Users/dylan/.cargo/registry/src/index.crates.io-6f17d22bba15001f/diesel-2.2.6/src/query_dsl/mod.rs:1542:15 + | +1540 | fn load<'query, U>(self, conn: &mut Conn) -> QueryResult> + | ---- required by a bound in this associated function +1541 | where +1542 | Self: LoadQuery<'query, Conn, U>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RunQueryDsl::load` + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-cdb3b656aa15b263.long-type-17907481964813576637.txt' + = note: consider using `--verbose` to print the full type name to the console + = note: the full name for the type has been written to '/Users/dylan/projects/order-diesel-jsonb-example/target/debug/deps/order_diesel_jsonb_example-cdb3b656aa15b263.long-type-2452195376562117312.txt' + = note: consider using `--verbose` to print the full type name to the console + = note: this error originates in the derive macro `Queryable` (in Nightly builds, run with -Z macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0277`. +warning: `order-diesel-jsonb-example` (bin "order-diesel-jsonb-example") generated 1 warning +error: could not compile `order-diesel-jsonb-example` (bin "order-diesel-jsonb-example") due to 4 previous errors; 1 warning emitted +``` + +## Fix: Change `metadata` type from `Value` to `Option` + +Next, let's see the error for `metadata` field. + +```bash +> cargo build + Compiling order-diesel-jsonb-example v0.1.0 +warning: unused import: `serde_json::json` + --> src/db.rs:3:5 + | +3 | use serde_json::json; + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` on by default + +error[E0277]: the trait bound `Value: FromSqlRow, Pg>` is not satisfied + --> src/models.rs:18:19 + | +18 | pub metadata: Value, // This will use JSONB + | ^^^^^ the trait `FromSql, Pg>` is not implemented for `Value`, which is required by `Value: FromSqlRow, Pg>` + | + = note: double check your type mappings via the documentation of `diesel::sql_types::Nullable` + = note: `diesel::sql_query` requires the loading target to column names for loading values. + You need to provide a type that explicitly derives `diesel::deserialize::QueryableByName` + = help: the following other types implement trait `FromSql`: + `Value` implements `FromSql` + `Value` implements `FromSql` + = note: required for `Value` to implement `diesel::Queryable, Pg>` + = note: required for `Value` to implement `FromSqlRow, Pg>` + = help: see issue #48214 +``` + +The reason for that error is that we declare `metadata` as `NOT NULL` in migration sql file, which is not aligned with `metadata` type in model defination. The fix is easy by changing datetype for `metadata` from `Value` to `Option`. + +```rust +#[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)] +#[diesel(table_name = orders)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct Order { + pub id: i32, + pub user_id: i32, + // pub total_amount: f64, + pub total_amount: BigDecimal, + pub order_date: NaiveDateTime, + // pub metadata: Value, // This will use JSONB ❌ + pub metadata: Option, // This will use JSONB ✅ +} +``` + +`metadata` column type: + +```sql +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL, + total_amount DECIMAL(10, 2) NOT NULL, + order_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + metadata JSONB -- 👈👈👈👈👈👈 🙋 +); +``` + +Now, let's build it. + +```bash + Compiling order-diesel-jsonb-example v0.1.0 +warning: unused import: `serde_json::json` + --> src/db.rs:3:5 + | +3 | use serde_json::json; + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` on by default + +warning: function `get_order_by_id` is never used + --> src/db.rs:15:8 + | +15 | pub fn get_order_by_id(conn: &mut PgConnection, order_id: i32) -> Result { + | ^^^^^^^^^^^^^^^ + | + = note: `#[warn(dead_code)]` on by default + +warning: function `get_orders_by_user` is never used + --> src/db.rs:21:8 + | +21 | pub fn get_orders_by_user(conn: &mut PgConnection, user_id_param: i32) -> Result> { + | ^^^^^^^^^^^^^^^^^^ + +warning: `order-diesel-jsonb-example` (bin "order-diesel-jsonb-example") generated 3 warnings (run `cargo fix --bin "order-diesel-jsonb-example"` to apply 1 suggestion) +``` + +🎉🎉🎉 + +We could also add `NOT NULL` to `metadata` column type and keep `metadata` as `Value` in model definition. + +# Create orders + +Finally, let's create some orders and see the result in database. + +```bash +cargo run --bin order-diesel-jsonb-example +``` + +Connect the database using `psql diesel_demo` and see the result: + +```sql +dylan@/tmp:diesel_demo> \d ++--------+----------------------------+----------+-------+ +| Schema | Name | Type | Owner | +|--------+----------------------------+----------+-------| +| public | __diesel_schema_migrations | table | dylan | +| public | orders | table | dylan | +| public | orders_id_seq | sequence | dylan | ++--------+----------------------------+----------+-------+ +SELECT 3 +Time: 0.008s +dylan@/tmp:diesel_demo> select * from orders; ++----+---------+--------------+----------------------------+-----------------------------------------------------------------------------+ +| id | user_id | total_amount | order_date | metadata | +|----+---------+--------------+----------------------------+-----------------------------------------------------------------------------| +| 1 | 1 | 0.80 | 2024-12-17 03:05:10.732408 | {"items": ["book", "pen"], "gift_wrap": true, "shipping_method": "express"} | ++----+---------+--------------+----------------------------+-----------------------------------------------------------------------------+ +SELECT 1 +Time: 0.006s +``` + +# Query orders + +As a next step, let's see how to query orders. + +Below is the query result for `SELECT * FROM orders WHERE metadata @> '{"address": "Article Circle Expressway 2"}'`: + +```sql +dylan@/tmp:diesel_demo> select * from orders; ++----+---------+--------------+----------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| id | user_id | total_amount | order_date | metadata | +|----+---------+--------------+----------------------------+-----------------------------------------------------------------------------------------------------------------------| +| 1 | 1 | 0.80 | 2024-12-17 03:05:10.732408 | {"items": ["book", "pen"], "gift_wrap": true, "shipping_method": "express"} | +| 2 | 1 | 0.80 | 2024-12-17 03:08:16.591275 | {"items": ["book", "pen"], "gift_wrap": true, "shipping_method": "express"} | +| 3 | 1 | 0.80 | 2024-12-17 05:46:41.173109 | {"items": ["book", "pen"], "address": "123 Main St, Anytown, USA", "gift_wrap": true, "shipping_method": "express"} | +| 4 | 1 | 0.80 | 2024-12-17 05:47:40.956483 | {"items": ["book", "pen"], "address": "Article Circle Expressway 2", "gift_wrap": true, "shipping_method": "express"} | ++----+---------+--------------+----------------------------+-----------------------------------------------------------------------------------------------------------------------+ +SELECT 4 +Time: 0.006s +dylan@/tmp:diesel_demo> +Time: 0.000s +dylan@/tmp:diesel_demo> +Time: 0.000s +dylan@/tmp:diesel_demo> SELECT * FROM orders WHERE metadata @> '{"address": "Article Circle Expressway 2"}'; ++----+---------+--------------+----------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| id | user_id | total_amount | order_date | metadata | +|----+---------+--------------+----------------------------+-----------------------------------------------------------------------------------------------------------------------| +| 4 | 1 | 0.80 | 2024-12-17 05:47:40.956483 | {"items": ["book", "pen"], "address": "Article Circle Expressway 2", "gift_wrap": true, "shipping_method": "express"} | ++----+---------+--------------+----------------------------+-----------------------------------------------------------------------------------------------------------------------+ +SELECT 1 +Time: 0.013s +``` + +Let's see how we can archieve the same result using Diesel. + +We can use operators for jsonb types: + +```rust +pub fn get_orders_by_address( + conn: &mut PgConnection, metadata: &serde_json::Value, +) -> QueryResult> { + use crate::schema::orders::dsl::{metadata as orders_metadata, orders}; + let query = orders.filter(orders_metadata.contains(metadata)); + let debug = diesel::debug_query::(&query); + println!("The insert query: {:#?}", debug); + query.get_results(conn) +} +``` + +The code above uses `contains` jsonb operator(`@>`) to query the orders by metadata. You can use `{"address": "Article Circle Expressway 2"}` as the creteria. + +Now, let's modify `main.rs` to call the `get_orders_by_address` method. + +```rust + +fn main() { + let conn = &mut establish_connection(); + + // // Example usage + // let new_order = models::NewOrder { + // user_id: 1, + // // total_amount: 99.99, + // total_amount: BigDecimal::from_str("0.80").unwrap(), + // metadata: serde_json::json!({ + // "items": ["book", "pen"], + // "shipping_method": "express", + // "gift_wrap": true, + // "address": "Article Circle Expressway 2", + // // "address": "123 Main St, Anytown, USA", + // }), + // }; + + // match db::create_order(conn, new_order) { + // Ok(order) => println!("Created order: {:?}", order), + // Err(e) => eprintln!("Error creating order: {}", e), + // } + + let metadata_address: serde_json::Value = serde_json::json!({"address": "Article Circle Expressway 2"}); + match db::get_orders_by_address(conn, &metadata_address) { + Ok(orders) => println!("Orders by address: {:#?}", orders), + Err(e) => eprintln!("Error getting orders by address: {}", e), + } +} +``` + +Running the query using `contains` jsonb operator(`@>`) gives us the same result. Notice the `@>` operator in the SQL query. + +```bash + Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.46s + Running `target/debug/order-diesel-jsonb-example` +The insert query: Query { + sql: "SELECT \"orders\".\"id\", \"orders\".\"user_id\", \"orders\".\"total_amount\", \"orders\".\"order_date\", \"orders\".\"metadata\" FROM \"orders\" WHERE (\"orders\".\"metadata\" @> $1)", + binds: [ + Object { + "address": String("Article Circle Expressway 2"), + }, + ], +} +Orders by address: [ + Order { + id: 4, + user_id: 1, + total_amount: BigDecimal("80e-2"), + order_date: 2024-12-17T05:47:40.956483, + metadata: Some( + Object { + "address": String("Article Circle Expressway 2"), + "gift_wrap": Bool(true), + "items": Array [ + String("book"), + String("pen"), + ], + "shipping_method": String("express"), + }, + ), + }, +] +``` + +Below is the source code for `contains` method in [diesel source code](https://github.com/diesel-rs/diesel/blob/b705023d85e6ef76292a427aa70f21c28a5902ff/diesel/src/pg/expression/expression_methods.rs#L2771). + +````rust +/// PostgreSQL specific methods present on JSONB expressions. +#[cfg(feature = "postgres_backend")] +pub trait PgJsonbExpressionMethods: Expression + Sized { + /// Creates a PostgreSQL `@>` expression. + /// + /// This operator checks whether left hand side JSONB value contains right hand side JSONB value + /// + /// # Example + /// + /// ```rust + /// # include!("../../doctest_setup.rs"); + /// # + /// # table! { + /// # contacts { + /// # id -> Integer, + /// # name -> VarChar, + /// # address -> Jsonb, + /// # } + /// # } + /// # + /// # fn main() { + /// # run_test().unwrap(); + /// # } + /// # + /// # #[cfg(feature = "serde_json")] + /// # fn run_test() -> QueryResult<()> { + /// # use self::contacts::dsl::*; + /// # let conn = &mut establish_connection(); + /// # diesel::sql_query("DROP TABLE IF EXISTS contacts").execute(conn).unwrap(); + /// # diesel::sql_query("CREATE TABLE contacts ( + /// # id SERIAL PRIMARY KEY, + /// # name VARCHAR NOT NULL, + /// # address JSONB NOT NULL + /// # )").execute(conn).unwrap(); + /// # + /// let easter_bunny_address: serde_json::Value = serde_json::json!({ + /// "street": "123 Carrot Road", + /// "province": "Easter Island", + /// "region": "Valparaíso", + /// "country": "Chile", + /// "postcode": "88888", + /// }); + /// diesel::insert_into(contacts) + /// .values((name.eq("Bunny"), address.eq(&easter_bunny_address))) + /// .execute(conn)?; + /// + /// let country_chile: serde_json::Value = serde_json::json!({"country": "Chile"}); + /// let contains_country_chile = contacts.select(address.contains(&country_chile)).get_result::(conn)?; + /// assert!(contains_country_chile); + /// # Ok(()) + /// # } + /// # #[cfg(not(feature = "serde_json"))] + /// # fn run_test() -> QueryResult<()> { + /// # Ok(()) + /// # } + /// ``` + fn contains(self, other: T) -> dsl::Contains + where + Self::SqlType: SqlType, + T: AsExpression, + { + Grouped(Contains::new(self, other.as_expression())) + } +} +```` + +# Update order + +Now, let's update the order by filtering the order by address. + +```rust +// write update order by address function +pub fn update_order_by_address( + conn: &mut PgConnection, address: &str, new_amount: BigDecimal, +) -> QueryResult { + use crate::schema::orders::dsl::{metadata, orders, total_amount}; + + let query = diesel::update(orders) + .filter(metadata.contains(json!({ "address": address }))) + .set(total_amount.eq(new_amount)); + + let debug = diesel::debug_query::(&query); + println!("The update query: {:#?}", debug); + + query.execute(conn) +} +``` + +This function will update the order by filtering the order by address. + +Now, let's modify `main.rs` to call the `update_order_by_address` method. + +```rust +fn main() { + let conn = &mut establish_connection(); + // // Example usage + // let new_order = models::NewOrder { + // user_id: 1, + // // total_amount: 99.99, + // total_amount: BigDecimal::from_str("0.80").unwrap(), + // metadata: serde_json::json!({ + // "items": ["book", "pen"], + // "shipping_method": "express", + // "gift_wrap": true, + // "address": "Article Circle Expressway 2", + // // "address": "123 Main St, Anytown, USA", + // }), + // }; + + // match db::create_order(conn, new_order) { + // Ok(order) => println!("Created order: {:?}", order), + // Err(e) => eprintln!("Error creating order: {}", e), + // } + + + // Query + // let metadata_address: serde_json::Value = serde_json::json!({"address": "Article Circle Expressway 2"}); + // match db::get_orders_by_address(conn, &metadata_address) { + // Ok(orders) => println!("Orders by address: {:#?}", orders), + // Err(e) => eprintln!("Error getting orders by address: {}", e), + // } + + // Update + let address = "Article Circle Expressway 2"; + let new_amount = BigDecimal::from_f64(1234.56).unwrap(); + match db::update_order_by_address(conn, address, new_amount) { + Ok(orders) => println!("Orders by address: {:#?}", orders), + Err(e) => eprintln!("Error getting orders by address: {}", e), + } +} +``` + +Below is the data changes after the update: + +```sql +dylan@/tmp:diesel_demo> SELECT * FROM orders WHERE metadata @> '{"address": "Article Circle Expressway 2"}'; ++----+---------+--------------+----------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| id | user_id | total_amount | order_date | metadata | +|----+---------+--------------+----------------------------+-----------------------------------------------------------------------------------------------------------------------| +| 4 | 1 | 0.80 | 2024-12-17 05:47:40.956483 | {"items": ["book", "pen"], "address": "Article Circle Expressway 2", "gift_wrap": true, "shipping_method": "express"} | ++----+---------+--------------+----------------------------+-----------------------------------------------------------------------------------------------------------------------+ +SELECT 1 +Time: 0.017s +dylan@/tmp:diesel_demo> +Time: 0.000s +dylan@/tmp:diesel_demo> +Time: 0.000s +dylan@/tmp:diesel_demo> +Time: 0.000s +dylan@/tmp:diesel_demo> SELECT * FROM orders WHERE metadata @> '{"address": "Article Circle Expressway 2"}'; ++----+---------+--------------+----------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| id | user_id | total_amount | order_date | metadata | +|----+---------+--------------+----------------------------+-----------------------------------------------------------------------------------------------------------------------| +| 4 | 1 | 1234.56 | 2024-12-17 05:47:40.956483 | {"items": ["book", "pen"], "address": "Article Circle Expressway 2", "gift_wrap": true, "shipping_method": "express"} | ++----+---------+--------------+----------------------------+-----------------------------------------------------------------------------------------------------------------------+ +SELECT 1 +Time: 0.007s +``` + +# Delete order + +Let's write the code to delete the order by filtering the order by address. + +```rust +// write delete order by address function +pub fn delete_order_by_address( + conn: &mut PgConnection, address: &str, +) -> QueryResult { + use crate::schema::orders::dsl::{metadata, orders}; + + let query = diesel::delete(orders) + .filter(metadata.contains(json!({ "address": address }))); + + let debug = diesel::debug_query::(&query); + println!("The delete query: {:#?}", debug); + + query.execute(conn) +} +``` + +Let's modify `main.rs` to call the `delete_order_by_address` method. + +```rust +fn main() { + let conn = &mut establish_connection(); + + // // Example usage + // let new_order = models::NewOrder { + // user_id: 1, + // // total_amount: 99.99, + // total_amount: BigDecimal::from_str("0.80").unwrap(), + // metadata: serde_json::json!({ + // "items": ["book", "pen"], + // "shipping_method": "express", + // "gift_wrap": true, + // "address": "Article Circle Expressway 2", + // // "address": "123 Main St, Anytown, USA", + // }), + // }; + + // match db::create_order(conn, new_order) { + // Ok(order) => println!("Created order: {:?}", order), + // Err(e) => eprintln!("Error creating order: {}", e), + // } + + + // Query + // let metadata_address: serde_json::Value = serde_json::json!({"address": "Article Circle Expressway 2"}); + // match db::get_orders_by_address(conn, &metadata_address) { + // Ok(orders) => println!("Orders by address: {:#?}", orders), + // Err(e) => eprintln!("Error getting orders by address: {}", e), + // } + + // Update + // let address = "Article Circle Expressway 2"; + // let new_amount = BigDecimal::from_f64(1234.56).unwrap(); + // match db::update_order_by_address(conn, address, new_amount) { + // Ok(orders) => println!("Orders by address: {:#?}", orders), + // Err(e) => eprintln!("Error getting orders by address: {}", e), + // } + + // Delete + let address = "Article Circle Expressway 2"; + match db::delete_order_by_address(conn, address) { + Ok(orders) => println!("Orders by address: {:#?}", orders), + Err(e) => eprintln!("Error getting orders by address: {}", e), + } +} +``` + +Below is the data changes after the delete: + +```sql +dylan@/tmp:diesel_demo> SELECT * FROM orders WHERE metadata @> '{"address": "Article Circle Expressway 2"}'; ++----+---------+--------------+----------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| id | user_id | total_amount | order_date | metadata | +|----+---------+--------------+----------------------------+-----------------------------------------------------------------------------------------------------------------------| +| 4 | 1 | 1234.56 | 2024-12-17 05:47:40.956483 | {"items": ["book", "pen"], "address": "Article Circle Expressway 2", "gift_wrap": true, "shipping_method": "express"} | ++----+---------+--------------+----------------------------+-----------------------------------------------------------------------------------------------------------------------+ +SELECT 1 +Time: 0.007s +dylan@/tmp:diesel_demo> +Time: 0.000s +dylan@/tmp:diesel_demo> +Time: 0.000s +dylan@/tmp:diesel_demo> +Time: 0.000s +dylan@/tmp:diesel_demo> SELECT * FROM orders WHERE metadata @> '{"address": "Article Circle Expressway 2"}'; ++----+---------+--------------+------------+----------+ +| id | user_id | total_amount | order_date | metadata | +|----+---------+--------------+------------+----------| ++----+---------+--------------+------------+----------+ +SELECT 0 +Time: 0.006s +``` + +# Summary + +In this demo, we have seen how to setup diesel and run migrations, write sql for migration, create orders, query orders, update orders, delete orders. + +We learned how to use Diesel to query the orders by metadata using the `@>` jsonb operator. + +We have also seen how to update the order by filtering the order by address and delete the order by filtering the order by address. + +# Refs + +Diesel schema +https://diesel.rs/guides/schema-in-depth.html + +Diesel getting started +https://diesel.rs/guides/getting-started diff --git a/src/rust/error/how-to-organise-application-error-in-actix-web-application.md b/src/rust/error/how-to-organise-application-error-in-actix-web-application.md new file mode 100644 index 0000000..97b0d71 --- /dev/null +++ b/src/rust/error/how-to-organise-application-error-in-actix-web-application.md @@ -0,0 +1,341 @@ +# How to organise application Error in actix-web application + +- [Into](#into) +- [Actix-web http layer](#actix-web-http-layer) +- [Solution](#solution) + - [Solution 1: implement `From` trait manually](#solution-1-implement-from-trait-manually) + - [Solution 2: use thiserror crate](#solution-2-use-thiserror-crate) + +# Into + +In this article, we will learn how to organise application Error in actix-web application. + +Every backend application has a dao layer(or data access object) and a http layer. + +For dao layer, usually we use `lib::Result` as return type. Below is an example of fetching all users when using `clickhosue-rs` crate: + +```rust +use clickhouse::error::{Error, Result}; + +pub async fn get_all_users(client: &Client) -> Result> { + let users = client + .query("SELECT ?fields FROM users") + .fetch_all::() + .await?; + + Ok(users) +} +``` + +Notice, the `Result` is not `std::result::Result` type, it is a type that use `clickhouse::error::Error` as the error type in `Result` generic type. + +```rust +use std::{error::Error as StdError, fmt, io, result, str::Utf8Error}; + +use serde::{de, ser}; + +/// A result with a specified [`Error`] type. +pub type Result = result::Result; + + +/// Represents all possible errors. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum Error { + #[error("invalid params: {0}")] + InvalidParams(#[source] Box), + #[error("network error: {0}")] + Network(#[source] Box), + #[error("compression error: {0}")] + Compression(#[source] Box), + #[error("decompression error: {0}")] + Decompression(#[source] Box), + #[error("no rows returned by a query that expected to return at least one row")] + RowNotFound, + #[error("sequences must have a known size ahead of time")] + SequenceMustHaveLength, + #[error("`deserialize_any` is not supported")] + DeserializeAnyNotSupported, + #[error("not enough data, probably a row type mismatches a database schema")] + NotEnoughData, + #[error("string is not valid utf8")] + InvalidUtf8Encoding(#[from] Utf8Error), + #[error("tag for enum is not valid")] + InvalidTagEncoding(usize), + #[error("a custom error message from serde: {0}")] + Custom(String), + #[error("bad response: {0}")] + BadResponse(String), + #[error("timeout expired")] + TimedOut, + + // Internally handled errors, not part of public API. + // XXX: move to another error? + #[error("internal error: too small buffer, need another {0} bytes")] + #[doc(hidden)] + TooSmallBuffer(usize), +} +``` + +# Actix-web http layer + +Actix-web framework requires the error type is `actix_web::error::Error` if `actix_web::Result` is used as the return type. + +`Result` type in actix-web : + +```rust +pub use self::error::Error; +pub use self::internal::*; +pub use self::response_error::ResponseError; +pub(crate) use macros::{downcast_dyn, downcast_get_type_id}; + +/// A convenience [`Result`](std::result::Result) for Actix Web operations. +/// +/// This type alias is generally used to avoid writing out `actix_http::Error` directly. +pub type Result = std::result::Result; +``` + +`Error` type in actix-web : + +```rust +/// General purpose Actix Web error. +/// +/// An Actix Web error is used to carry errors from `std::error` through actix in a convenient way. +/// It can be created through converting errors with `into()`. +/// +/// Whenever it is created from an external object a response error is created for it that can be +/// used to create an HTTP response from it this means that if you have access to an actix `Error` +/// you can always get a `ResponseError` reference from it. +pub struct Error { + cause: Box, +} +``` + +Normally, we write actix-web handler and call dao functions like this: + +```rust +pub async fn get_all_users(data: web::Data) -> actix_web::Result { + let db = &data.db; + + // call dao function to fetch data + let users = users::get_all_users(db).await?; + Ok(web::Json(users)) +} +``` + +If you write a http handler like this and call function in dao(i.e. `dao::get_all_users`) function, which returns an error from the dao crate, error will happen. + +```rust + --> src/user/http.rs:348:54 + | +348 | let users = users::get_all(db).await?; + | ^ the trait `ResponseError` is not implemented for `clickhouse::error::Error` + | + = help: the following other types implement trait `ResponseError`: + AppError + BlockingError + Box<(dyn StdError + 'static)> + HttpError + Infallible + InvalidHeaderValue + JsonPayloadError + PathError + and 17 others + = note: required for `actix_web::Error` to implement `std::convert::From` + = note: required for `Result<_, actix_web::Error>` to implement `FromResidual>` +``` + +It means that you cannot convert `clickhouse::error::Error` to `actix_web::Error`. + +The reason is that the error type in dao function is `clickhouse::error::Error`, not `actix_web::Error`. While in http handler, the error type is `actix_web::Error` . You have to implement `From` trait as rust compiler told you. + +# Solution + +## Solution 1: implement `From` trait manually + +One possible solution is to define application error and implement `From` trait, which will convert `clickhouse::error::Error` to `AppError`: + +```rust +// Define your application error in enum +#[derive(Debug)] +pub enum AppError { + ClickhouseError(ClickhouseError), + // ... other error variants +} + +impl ResponseError for AppError { + fn error_response(&self) -> HttpResponse { + HttpResponse::InternalServerError().body(self.to_string()) + // We can also use match to handle specific error define in clickhouse::error::Error + // match *self { + // AppError::ClickhouseError(ref err) => match err { + // ClickhouseError::Timeout(err) => HttpResponse::InternalServerError() + // .body(format!("Clickhouse server error: {}", err)), + // ClickhouseError::Network(err) => { + // HttpResponse::BadRequest().body(format!("Clickhouse client error: {}", err)) + // } + // _ => HttpResponse::InternalServerError().body("Unknown error"), + // }, // ... handle other error variants + // } + } +} + +use std::fmt; + +impl fmt::Display for AppError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + AppError::ClickhouseError(ref err) => { + write!(f, "Clickhouse error: {}", err) + } // ... handle other error variants + } + } +} + +impl From for AppError { + fn from(error: ClickhouseError) -> Self { + AppError::ClickhouseError(error) + } +} +``` + +Finally, you need to modify actix-web handler: + +```rust +use clickhouse::error::{Error, Result}; + +// Previous function signature +// pub async fn get_all_users(data: web::Data) -> actix_web::Result { +// Pass AppError to handler's return type +pub async fn get_all_users(data: web::Data) -> actix_web::Result { + let db = &data.db; + + // call dao function to fetch data + let users = users::get_all_users(db).await?; + Ok(web::Json(users)) +} +``` + +🎉🎉🎉 + +## Solution 2: use thiserror crate + +Another solution is to use thiserror crate. + +This crate will automatically implement `From` trait, which will do the conversion. + +```rust +use thiserror::Error; +use clickhouse::error::Error as ClickhouseError; +use actix_web::{HttpResponse, ResponseError}; + +#[derive(Debug, Error)] +pub enum AppError { + #[error("Clickhouse error: {0}")] + ClickhouseError(#[from] ClickhouseError), + + // You can add more error variants as needed + #[error("Database connection error")] + DatabaseConnectionError, + + #[error("Internal server error: {0}")] + InternalError(String), +} + +impl ResponseError for AppError { + fn error_response(&self) -> HttpResponse { + match self { + AppError::ClickhouseError(_) => { + HttpResponse::InternalServerError().body(self.to_string()) + } + AppError::DatabaseConnectionError => { + HttpResponse::ServiceUnavailable().body(self.to_string()) + } + AppError::InternalError(_) => { + HttpResponse::InternalServerError().body(self.to_string()) + } + } + } +} +``` + +Here is the key improvements with `thiserror`: + +- The `#[derive(Error)]` automatically implements `std::error::Error`. +- `#[error("...")]` provides a convenient way to implement `Display` trait. +- `#[from]` attribute automatically implements `From` trait for error conversion. +- The code is more concise and readable. +- You can easily add more error variants with custom error messages. + +Benefits of this approach: + +- Automatic error conversion +- Clear, descriptive error messages +- Easy to extend with new error types +- Consistent error handling across the application + +The `ResponseError` implementation allows you to: + +- Map different error types to appropriate HTTP status codes +- Provide meaningful error responses +- Easily customize error handling for different error variants + +Note: Make sure to import necessary types and traits from the appropriate modules (actix-web, thiserror, etc.). + +Below is the example of using thiserror crate: + +```rust +// use actix_web::{get, post, web, App, HttpRequest, HttpResponse, HttpServer, Responder}; +use actix_web::{ web, App, HttpServer, Responder}; +use clickhouse::Client; +use clickhouse_example::{dao::get_all_users, error::AppError}; + +// This struct represents state +pub(crate) struct AppState { + pub app_name: String, + pub db: Client, +} + +// NOTE: This function is not working because of error type mismatch +// async fn get_users( +// data: web::Data, +// ) -> actix_web::Result { +// let db = &data.db; + +// // call dao function to fetch data +// let users = get_all_users(db).await?; +// Ok(web::Json(users)) +// } + +// Handler function +pub(crate) async fn get_users(data: web::Data) -> actix_web::Result { + let db = &data.db; + + // call dao function to fetch data + let users = get_all_users(db).await?; + Ok(web::Json(users)) +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + let url = "http://localhost:8123"; + let database = "default"; + let user = "test"; + let password = "secret"; + let client = Client::default() + .with_url(url) + .with_user(user) + .with_password(password) + .with_database(database); + + HttpServer::new(move || { + App::new() + .app_data(web::Data::new(AppState {db:client.clone(), app_name: "My App".into() })) + .route("/users", web::get().to(get_users)) + }) + .bind(("127.0.0.1", 8080))? + .run() + .await +} +``` diff --git a/src/rust/error/return-error-when-unwrap-option-when-none.md b/src/rust/error/return-error-when-unwrap-option-when-none.md new file mode 100644 index 0000000..7765822 --- /dev/null +++ b/src/rust/error/return-error-when-unwrap-option-when-none.md @@ -0,0 +1,161 @@ +# Return error when unwrap Option when None + +- [Intro](#intro) +- [Application error](#application-error) +- [Function returns Result](#function-returns-result) +- [Solution](#solution) + - [Use `match`](#use-match) + - [Use `ok_or_else`](#use-ok_or_else) + +# Intro + +In this blog, we will learn how to handle optional values and return errors in Actix-Web Handlers. + +In Rust, dealing with optional values (`Option`) and converting them to errors in web handlers is a common task. This blog explores different strategies to handle cases where an expected value is `None`. + +When working with optional values, you have several idiomatic Rust approaches: + +- Using `match` to Convert `None` to an Error + + - Explicitly match on the `Option` type + - Explicitly return an error when the value is `None` + +- Using `ok_or_else()` Method + - Provides a concise way to convert `Option` to `Result` + - Allows lazy error generation + - Avoids unnecessary error creation if not needed + +Let's explore these approaches with practical examples. + +# Application error + +Suppose we define our application error like this: + +```rust +use actix_web::{HttpResponse, ResponseError}; +use clickhouse::error::Error as ClickhouseError; +use std::fmt; + +#[derive(Debug)] +pub enum AppError { + ClickhouseError(ClickhouseError), + ScheduleError(String), + SQLGenError(String), +} + +impl ResponseError for AppError { + fn error_response(&self) -> HttpResponse { + HttpResponse::InternalServerError().body(self.to_string()) + // match *self { + // AppError::ClickhouseError(ref err) => match err { + // ClickhouseError::Server(err) => HttpResponse::InternalServerError() + // .body(format!("Clickhouse server error: {}", err)), + // ClickhouseError::Client(err) => { + // HttpResponse::BadRequest().body(format!("Clickhouse client error: {}", err)) + // } + // _ => HttpResponse::InternalServerError().body("Unknown error"), + // }, // ... handle other error variants + // } + } +} + +impl fmt::Display for AppError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + AppError::ClickhouseError(ref err) => { + write!(f, "Clickhouse error: {}", err) + } + AppError::ScheduleError(ref err) => { + write!(f, "Schedule error: {}", err) + } + AppError::SQLGenError(ref err) => { + write!(f, "SQLGen error: {}", err) + } + } + } +} + +impl From for AppError { + fn from(error: ClickhouseError) -> Self { + AppError::ClickhouseError(error) + } +} +``` + +# Function returns Result + +If we have a function which will return `Result` type: + +```rust +fn sql_gen_visualization_barchart( + query: &VisualizationQuery, + table_name: &str, + //) -> Result> { + //) -> anyhow::Result { +) -> anyhow::Result { + // gen sql for visualization + let mut sql = String::new(); + let field = query.field.as_ref(); + // ... +} +``` + +# Solution + +If we would like to return error when one of parameters is empty, we can do as follows. + +## Use `match` + +```rust +let field = match field { + Some(field) => field, + None => return Err(AppError::SQLGenError("Field is empty".to_string())), +}; +``` + +By using `match`, we can easily return an error when `field` is None. + +## Use `ok_or_else` + +If we don't like the `match`, we can leverage `ok_or_else` method, which will do the same way as using `match`. + +```rust +let field = query + .field + .as_ref() + .ok_or_else(|| AppError::SQLGenError("Field is empty".to_string()))?; +``` + +Below is the source code of `ok_or_else` method: + +````rust +impl Option { + /// Transforms the `Option` into a [`Result`], mapping [`Some(v)`] to + /// [`Ok(v)`] and [`None`] to [`Err(err())`]. + /// + /// [`Ok(v)`]: Ok + /// [`Err(err())`]: Err + /// [`Some(v)`]: Some + /// + /// # Examples + /// + /// ``` + /// let x = Some("foo"); + /// assert_eq!(x.ok_or_else(|| 0), Ok("foo")); + /// + /// let x: Option<&str> = None; + /// assert_eq!(x.ok_or_else(|| 0), Err(0)); + /// ``` + #[inline] + #[stable(feature = "rust1", since = "1.0.0")] + pub fn ok_or_else(self, err: F) -> Result + where + F: FnOnce() -> E, + { + match self { + Some(v) => Ok(v), + None => Err(err()), + } + } +} +```` diff --git a/src/rust/grpc/rust-grpc-helloworld.md b/src/rust/grpc/rust-grpc-helloworld.md new file mode 100644 index 0000000..b372ec1 --- /dev/null +++ b/src/rust/grpc/rust-grpc-helloworld.md @@ -0,0 +1,531 @@ +# Rust grpc helloworld + +- [Init the project](#init-the-project) +- [Write protocol file](#write-protocol-file) +- [Write build.rs](#write-buildrs) +- [Write helloworld grpc server](#write-helloworld-grpc-server) +- [Write helloworld grpc client](#write-helloworld-grpc-client) +- [Run helloworld grpc server](#run-helloworld-grpc-server) +- [Run helloworld grpc client](#run-helloworld-grpc-client) +- [Generated helloworld.rs](#generated-helloworldrs) + +In this tutorial, we'll walk you through how to setup a gRPC server in Rust using `tonic` crate. + +# Init the project + +First, we'll create a new project using `cargo init `. + +```bash +cargo init tonic_example +``` + +Let's see the structure of the directory. + +```bash +❯ tree +. +├── Cargo.toml +└── src + └── main.rs + +2 directories, 2 files + +~/tmp/tonic_example master* + +❯ ct Cargo.toml +[package] +name = "tonic_example" +version = "0.1.0" +edition = "2021" + +[dependencies] +``` + +As we'll build a grpc service, we can use `tonic` crate, which is a Rust implementation of gRPC. + +# Write protocol file + +We'll create a simple greeter service. We'll put proto file in `proto` directory. + +```bash +mkdir proto +touch proto/helloworld.proto +``` + +Here's the content of the `helloworld.proto` file, in which we define `HelloRequest` type, `HelloReply` type and `Greeter` service. + +```proto +syntax = "proto3"; +package helloworld; + +service Greeter { + // Our SayHello rpc accepts HelloRequests and returns HelloReplies + rpc SayHello (HelloRequest) returns (HelloReply); +} + +message HelloRequest { + // Request message contains the name to be greeted + string name = 1; +} + +message HelloReply { + // Reply contains the greeting message + string message = 1; +} +``` + +Here's an explanation of the code: + +- `syntax = "proto3";`: This line indicates that you are using version 3 of the protobuf language. +- `package helloworld;`: This line defines the package name for the service. It helps to prevent name clashes between protobuf messages. + +The service definition starts with this line: `service Greeter {`. Here are the key points: + +- `Greeter`: This is the name of the service (essentially an API) you are defining. +- The service `Greeter` has a single method `SayHello` which is defined as:`rpc SayHello (HelloRequest) returns (HelloReply);` + - `SayHello`: This is the name of the function that will be exposed to clients on the gRPC server. + - `(HelloRequest)`: This denotes the input parameters of the method. It takes in a single parameter of the type `HelloRequest`. + - `returns (HelloReply)`: This shows that the function returns a `HelloReply` message. + +The protocol buffer message types are defined with this code: + +- `.message HelloRequest`: The `HelloRequest` message has a single field name of type `string`. The `= 1;` bit is a unique number used to identify the field in the message binary format. +- `.message HelloReply`: The `HelloReply` message also has a single field message also of type `string`. + +In a nutshell, you have defined a `Greeter` service that has a `SayHello` method expecting a `HelloRequest` that contains a name and returns a `HelloReply` containing the message. It's analogous to defining a REST API endpoint but in the gRPC and protocol buffers context. + +# Write build.rs + +In order to build protobuf file, we need to install the `protoc` protocol buffers compiler. On MacOS, we can install by using this command: + +```bash +brew install protobuf +``` + +# Write helloworld grpc server + +Now, let's write server side code. + +Create a file `helloworld-server.rs` in `src/bin` directory: `touch src/bin/helloworld-server.rs`. + +```rust +use tonic::{transport::Server, Request, Response, Status}; + +use hello_world::{ + greeter_server::{Greeter, GreeterServer}, + HelloReply, HelloRequest, +}; + +pub mod hello_world { + tonic::include_proto!("helloworld"); +} + +#[derive(Debug, Default)] +pub struct MyGreeter {} + +#[tonic::async_trait] +impl Greeter for MyGreeter { + async fn say_hello( + &self, request: Request, + ) -> Result, Status> { + // println!("Got a request: {:?}", request); + + let reply = hello_world::HelloReply { + message: format!("Hello {}!", request.into_inner().name).into(), + }; + + Ok(Response::new(reply)) + } +} + +#[tokio::main] +// #[tokio::main(core_threads = 16, max_threads = 32)] +async fn main() -> Result<(), Box> { + // NOTE: This works! + let addr = "[::1]:50051".parse()?; + // NOTE❌: This does NOT works! ConnectionRefused error + // let addr = "0.0.0.0:50051".parse()?; + let greeter = MyGreeter::default(); + + Server::builder() + .add_service(GreeterServer::new(greeter)) + .serve(addr) + .await?; + + Ok(()) +} +``` + +# Write helloworld grpc client + +Now, let's write client side code. + +Create a file `helloworld-client.rs` in `src/bin` directory: `touch src/bin/helloworld-client.rs`. + +```rust +use hello_world::greeter_client::GreeterClient; +use hello_world::HelloRequest; + +pub mod hello_world { + tonic::include_proto!("helloworld"); +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut client = GreeterClient::connect("http://[::1]:50051").await?; + + let request = tonic::Request::new(HelloRequest { name: "Tonic".into() }); + + let response = client.say_hello(request).await?; + + println!("RESPONSE={:?}", response); + + Ok(()) +} +``` + +# Run helloworld grpc server + +Now, let's run the grpc server. + +```bash +cargo run --bin helloworld-server +``` + +# Run helloworld grpc client + +While the server is up and running, we can run the grpc client to send request to the server. + +```bash +cargo run --bin helloworld-client +``` + +Output: + +```bash + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s + Running `target/debug/helloworld-client` +RESPONSE=Response { + metadata: MetadataMap { + headers: { + "content-type": "application/grpc", + "date": "Wed, 03 Apr 2024 17:56:21 GMT", + "grpc-status": "0", + }, + }, + message: HelloReply { + message: "Hello Tonic!", + }, + extensions: Extensions, +} +``` + +# Generated helloworld.rs + +If you're interested in what the generated file looks like, you can refer to `helloworld.rs` file which is located in `target/debug/build/tonic_example-4094918d1c86be5c/out` directory. + +Below is the contents of the file. + +```rust +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct HelloRequest { + /// Request message contains the name to be greeted + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct HelloReply { + /// Reply contains the greeting message + #[prost(string, tag = "1")] + pub message: ::prost::alloc::string::String, +} +/// Generated client implementations. +pub mod greeter_client { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct GreeterClient { + inner: tonic::client::Grpc, + } + impl GreeterClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl GreeterClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + Send + 'static, + ::Error: Into + Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> GreeterClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + Send + Sync, + { + GreeterClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + /// Our SayHello rpc accepts HelloRequests and returns HelloReplies + pub async fn say_hello( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/helloworld.Greeter/SayHello", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("helloworld.Greeter", "SayHello")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod greeter_server { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with GreeterServer. + #[async_trait] + pub trait Greeter: Send + Sync + 'static { + /// Our SayHello rpc accepts HelloRequests and returns HelloReplies + async fn say_hello( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + } + #[derive(Debug)] + pub struct GreeterServer { + inner: _Inner, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + struct _Inner(Arc); + impl GreeterServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + let inner = _Inner(inner); + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for GreeterServer + where + T: Greeter, + B: Body + Send + 'static, + B::Error: Into + Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + let inner = self.inner.clone(); + match req.uri().path() { + "/helloworld.Greeter/SayHello" => { + #[allow(non_camel_case_types)] + struct SayHelloSvc(pub Arc); + impl tonic::server::UnaryService + for SayHelloSvc { + type Response = super::HelloReply; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::say_hello(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SayHelloSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + Ok( + http::Response::builder() + .status(200) + .header("grpc-status", "12") + .header("content-type", "application/grpc") + .body(empty_body()) + .unwrap(), + ) + }) + } + } + } + } + impl Clone for GreeterServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + impl Clone for _Inner { + fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) + } + } + impl std::fmt::Debug for _Inner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } + } + impl tonic::server::NamedService for GreeterServer { + const NAME: &'static str = "helloworld.Greeter"; + } +} +``` diff --git a/src/rust/serde/serialize-time-offsetdatetime-type-using-serde-as-in-serde-with-crate.md b/src/rust/serde/serialize-time-offsetdatetime-type-using-serde-as-in-serde-with-crate.md new file mode 100644 index 0000000..afe1970 --- /dev/null +++ b/src/rust/serde/serialize-time-offsetdatetime-type-using-serde-as-in-serde-with-crate.md @@ -0,0 +1,397 @@ +# Serialize time::OffsetDataTime type using serde_as in serde_with crate + +- [Table design](#table-design) +- [Database management using sqlx](#database-management-using-sqlx) +- [Writing Data Access Layer](#writing-data-access-layer) +- [Write actix handler](#write-actix-handler) + - [AppState](#appstate) + - [Actix handler](#actix-handler) + - [HttpServer setup](#httpserver-setup) + - [Config routes](#config-routes) + - [Run the application](#run-the-application) +- [Request data through api](#request-data-through-api) +- [Choose correct serialize method](#choose-correct-serialize-method) +- [Request data through api after using serde_as](#request-data-through-api-after-using-serde_as) + +# Table design + +When we develop backend api using postgres and sqlx, you will definitely use date in your database design. Take user table as an example: + +```sql +-- Add migration script here +CREATE TYPE gender AS ENUM ('male', 'female', 'other'); + +-- Table `users` +CREATE TABLE + IF NOT EXISTS users ( + id BIGSERIAL PRIMARY KEY, + username TEXT UNIQUE NOT NULL, + gender GENDER NOT NULL, + disabled BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW () + ); +``` + +In the provided example, the `users` table includes a `created_at` column of type `TIMESTAMPTZ`. + +In PostgreSQL, the `TIMESTAMPTZ` data type stands for "timestamp with time zone." + +# Database management using sqlx + +We can also use `sqlx` to generate migration and add the sql above to the migration script. + +The following example will use `sqlx`, which is [SQLx](https://github.com/launchbadge/sqlx)'s associated command-line utility for managing databases, migrations, to create database and generate the migration. + +```bash +# create database +DATABASE_URL=postgres://localhost/test sqlx database create + +# create migration +DATABASE_URL=postgres://localhost/test sqlx migrate add user +``` + +We can list all migration scripts in `migrations` direction, which is generated by `sqlx migrate add user` command. + +``` +drwxr-xr-x - username 22 Aug 14:22 migrations +.rw-r--r-- 334 username 22 Aug 14:22 └── 20230822062052_user.sql +``` + +We added above sql to file `20230822062052_user.sql` and `sqlx` will handle the migration. + +# Writing Data Access Layer + +We can write an `all_users` function to fetch data from database using `sqlx` crate. + +```rust +use serde::Serialize; +use sqlx::PgPool; +use time::OffsetDateTime; + +#[derive(Debug, Serialize)] +pub struct User { + pub id: i64, + pub username: String, + pub disabled: bool, + pub gender: Gender, + pub created_at: OffsetDateTime, +} + +#[derive(Clone, PartialEq, PartialOrd, Serialize, sqlx::Type, Debug)] +#[sqlx(type_name = "gender")] +#[sqlx(rename_all = "lowercase")] +pub enum Gender { + Male, + Female, + Other, +} + +impl User { + pub async fn all(connection: &PgPool) -> Result, sqlx::Error> { + let users = sqlx::query_as!( + User, + r#" + SELECT + id, + username, + gender as "gender: _", + disabled, + created_at + FROM users + "# + ) + .fetch_all(connection) + .await?; + + Ok(users) + } +} +``` + +The code snippet above uses the `sqlx` crate to interact with a PostgreSQL database and retrieve user data. + +- The `User` struct represents a user entity and is serialized using the serde crate. It contains fields such as `id`, `username`, `disabled`, `gender`, and `created_at`, representing the corresponding columns in the database table. +- The `Gender` enum represents the possible genders a user can have. It is derived from Clone, PartialEq, PartialOrd, and Serialize. The `sqlx::Type` trait is implemented to specify that this `enum` should be treated as a PostgreSQL custom type named "gender". The `sqlx(rename_all)` attribute is used to specify that the enum variants should be serialized in lowercase. You can refer to [rename_all](https://docs.rs/sqlx/latest/sqlx/trait.FromRow.html#rename_all) for more details. + If you don't specify `sqlx(rename_all)`, an error will occur: + +``` +thread 'actix-rt|system:0|arbiter:0' panicked at 'called `Result::unwrap()` on an `Err` value: ColumnDecode { index: "2", source: "invalid value \"male\" for enum Gender" }', enum-example/src/bin/enum.rs:33:45 +``` + +- The `User` struct also contains an `all` function that retrieves all users from the database. It takes a reference to a `PgPool` connection pool as a parameter and returns a `Result` with a vector of `User` instances or an `sqlx::Error` if an error occurs. +- Inside the `all` function, a SQL query is defined using the `sqlx::query_as!` macro. It selects the necessary columns from the `users` table, including mapping the `gender` column to the `Gender` enum using the as `"gender: _"` syntax. +- Finally, the `fetch_all` method is called on the query to execute it and retrieve all rows as a vector of User instances. The result is then returned as a Result. + +# Write actix handler + +## AppState + +Once we have the code implemented, let's see how we can use it to retrieve user data from a PostgreSQL database. + +First, we define `AppState` struct to represent the server's state, which contains two fields: `app_name`, a string representing the application name, and `pool`, a `PgPool` instance representing the connection pool to the PostgreSQL database. + +You can also add more to `AppState`, i.e. redis client to exchange data from Redis or kafka client to send or receive messages from Kafka, etc. + +```rust +// This struct represents state +struct AppState { + app_name: String, + pool: PgPool, +} +``` + +## Actix handler + +Then, we define a handler for retrieving all users. + +```rust +async fn all_users(data: web::Data) -> Result { + let connection = &data.pool; + let users = User::all(connection).await.unwrap(); + Ok(web::Json(users)) +} +``` + +The `all_users` function is an asynchronous handler that retrieves all users from the database. It takes a `web::Data` parameter containing the shared `AppState` data. Inside the function, it accesses the `PgPool` instance from the shared data and uses the `User` model to fetch all users from the database asynchronously. + +## HttpServer setup + +Next, we will create a `PgPool` instance and store the pool in application state variable, pass in a `Data::new(AppState { ... })` instance using `app_data` method. + +```rust +use sqlx::postgres::{PgPool, PgPoolOptions}; + +async fn main() -> std::io::Result<()> { + env::set_var("RUST_LOG", "info"); + env_logger::init(); + + let db_url = "postgres://localhost/test"; + let pool = connect(db_url).await.unwrap(); + HttpServer::new(move || { + App::new() + // .app_data(pool.clone()) + .app_data(Data::new(AppState { + app_name: "enum".into(), + pool: pool.clone(), + })) + .service(web::scope("/api/v1").configure(config)) + .route("/health", web::get().to(health)) + }) + .bind(("0.0.0.0", 8080))? + .run() + .await +} + +/// Open a connection to a database +pub async fn connect(db_url: &str) -> sqlx::Result { + // NOTE: older version of sqlx use PgPool, for newer version use + // PgPoolOptions::new to create a pool + // + // let pool = PgPool::new(db_url).await?; + + // Create a connection pool + let pool = PgPoolOptions::new() + .max_connections(5) + // .connect("postgres://localhost/test") + // .connect(&env::var("DATABASE_URL")?) + .connect(db_url) + .await?; + Ok(pool) +} +``` + +## Config routes + +Finally, we will configure routes for the application. + +We can use `configure` method to configure routes by passing an `function` with `F: FnOnce(&mut ServiceConfig)` trait bound like this: + +```rust +HttpServer::new(move || { + App::new() + // .app_data(pool.clone()) + .app_data(Data::new(AppState { + app_name: "enum".into(), + pool: pool.clone(), + })) + // config routers + .service(web::scope("/api/v1").configure(config)) + .route("/health", web::get().to(health)) +}) +.bind(("0.0.0.0", 8080))? +.run() +.await +``` + +Here is the signature for `configure` method: + +```rust + pub fn configure(mut self, cfg_fn: F) -> Self + where + F: FnOnce(&mut ServiceConfig); +``` + +And our config method: + +```rust +use actix_web::{ + web::{self, Data, ServiceConfig}, + web::{get, post, resource as r, scope}, + App, Error, HttpRequest, HttpResponse, HttpServer, Responder, Result, +}; + +// this function could be located in different module +pub fn config(cfg: &mut ServiceConfig) { + cfg + // users + .service(scope("/users").service( + r("").route(get().to(all_users)), // .route(post().to(delete_user)), + )); +} +``` + +## Run the application + +The application is configured with routes using the `service` and `route` methods. It includes a scope for API versioning with `/api/v1` and sets up a route for a health check endpoint ("/health") and a route to retrieve all users ("/users"). + +With all things tied up, we can run application using `cargo run` or `cargo run --bin ` if you have multiple binaries in you project: + +``` + Finished dev [unoptimized + debuginfo] target(s) in 2.73s + Running `target/debug/enum` +[2023-08-23T02:00:37Z INFO actix_server::builder] starting 10 workers +[2023-08-23T02:00:37Z INFO actix_server::server] Actix runtime found; starting in Actix runtime +``` + +# Request data through api + +Now it's time to test the api. + +We can request the user data through `/api/v1/users` endpoint: + +```bash +curl '0:8080/api/v1/users' | jq +``` + +Output: + +```json +[ + { + "id": 1, + "username": "john_doe", + "disabled": false, + "gender": "Male", + "created_at": [2023, 234, 15, 3, 34, 422482000, 0, 0, 0] + }, + { + "id": 2, + "username": "jane_smith", + "disabled": true, + "gender": "Female", + "created_at": [2023, 234, 15, 3, 34, 422482000, 0, 0, 0] + }, + { + "id": 3, + "username": "alex_jones", + "disabled": false, + "gender": "Other", + "created_at": [2023, 234, 15, 3, 34, 422482000, 0, 0, 0] + } +] +``` + +We have some trouble. The `created_at` is returned as an array, which should be a string like this: `2023-08-22T15:03:34.422482Z`. How to solve this problem? + +# Choose correct serialize method + +To fix the serialization problem of `OffsetDataTime` data type in `User` struct, we need to specify corrent serialization method for `created_at` field. + +We can use `serce_with` crate and use `Rfc3339` in `serde_as` macro, which will serialize `OffsetDataTime` like this `1985-04-12T23:20:50.52Z` instead of an array of integers `[2023, 234, 15, 3, 34, 422482000, 0, 0, 0]`. + +```rust +/// Well-known formats, typically standards. +pub mod well_known { + pub mod iso8601; + mod rfc2822; + mod rfc3339; + + #[doc(inline)] + pub use iso8601::Iso8601; + pub use rfc2822::Rfc2822; + pub use rfc3339::Rfc3339; +} +``` + +You can use `serde_with` crate as follows: + +- Place the `#[serde_as]` attribute before the `#[derive]` attribute. +- Use `#[serde_as(as = "...")`] instead of `#[serde(with = "...")]` to annotate field in struct + +Below is an example of using `serde_with` together with `serde_as` for `User` struct. + +```rust +use time::format_description::well_known::Rfc3339; + +#[serde_with::serde_as] +#[derive(Debug, Serialize)] +pub struct User { + pub id: i64, + pub username: String, + pub disabled: bool, + pub gender: Gender, + #[serde_as(as = "Rfc3339")] + pub created_at: OffsetDateTime, +} + +#[derive(Clone, PartialEq, PartialOrd, Serialize, sqlx::Type, Debug)] +#[sqlx(type_name = "gender")] +#[sqlx(rename_all = "lowercase")] +pub enum Gender { + Male, + Female, + Other, +} +``` + +Notice, we use `#[serde_as(as = "Rfc3339")]` to annotate `created_at` field with `OffsetDataTime` type. + +It's quite convenient to use. + +# Request data through api after using serde_as + +Now, when we request the data, we get the datetime as we wanted. + +```bash +curl '0:8080/api/v1/users' | jq +``` + +Output: + +```json +[ + { + "id": 1, + "username": "john_doe", + "disabled": false, + "gender": "Male", + "created_at": "2023-08-22T15:03:34.422482Z" + }, + { + "id": 2, + "username": "jane_smith", + "disabled": true, + "gender": "Female", + "created_at": "2023-08-22T15:03:34.422482Z" + }, + { + "id": 3, + "username": "alex_jones", + "disabled": false, + "gender": "Other", + "created_at": "2023-08-22T15:03:34.422482Z" + } +] +``` + +🎉🎉🎉 diff --git a/src/rust/tokio/async-healthcheck-multiple-endpoints.md b/src/rust/tokio/async-healthcheck-multiple-endpoints.md new file mode 100644 index 0000000..9400822 --- /dev/null +++ b/src/rust/tokio/async-healthcheck-multiple-endpoints.md @@ -0,0 +1,144 @@ +# Async Healthcheck Multiple Endpoints + +- [Intro](#intro) +- [Code](#code) +- [Code explain](#code-explain) + +## Intro + +Today, I'll show you how to use tokio to do healthcheck for multiple endpoints. + +The architecture is simple: + +- Initialized vector of healthcheck endpoints +- Spawn futures to do health check + +## Code + +```rust +// use tokio::time::sleep; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::io::BufReader; +use std::time::Duration; +use tokio::select; +use tokio::sync::mpsc; +use tokio::time; +use tokio::time::{interval, sleep}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct Config { + interval: u64, + url: String, +} + +async fn check_url(config: Config) { + loop { + println!("In check_url loop"); + let url = &config.url; + match reqwest::get(url).await { + Err(e) => println!("Error: Failed to access {}: {}", config.url, e), + Ok(response) => { + // println!("{response:?}"); + if !response.status().is_success() { + println!( + "Error: {} returned status code {}", + config.url, + response.status() + ); + } + + println!("check for {url} OK"); + } + } + sleep(Duration::from_secs(config.interval)).await; + } +} + +#[tokio::main] +async fn main() { + // Load configuration from file + // let file = File::open("config.json").expect("Failed to open config file"); + // let reader = BufReader::new(file); + // let configs: Vec = + // serde_json::from_reader(reader).expect("Failed to parse config file"); + + let configs = vec![ + Config { interval: 10, url: "http://www.baidu.com".to_string() }, + Config { interval: 10, url: "http://www.qq.com".to_string() }, + ]; + + // Create a shared timer + // let mut ticker = interval(Duration::from_secs(1)); + + // let mut interval = + // time::interval(time::Duration::from_millis(consume_interval)); + + // Create a task for each URL and spawn it + // + // NOTE: we don't need to run in loop in spawn, check_url already has loop + // for config in configs { + // // let mut tick = ticker.tick(); + // tokio::spawn(async move { + // let mut ticker = interval(Duration::from_secs(1)); + // loop { + // select! { + // // _ = tick => { + // _ = ticker.tick() => { + // println!("1s ..."); + // check_url(config.clone()).await; + // } + // } + // } + // }); + // } + + for config in configs { + tokio::spawn(async move { + println!("spawn check future ..."); + check_url(config.clone()).await; + }); + } + + println!("Infinite loop"); + // Keep the program running so that other tasks can continue to run + time::sleep(Duration::from_secs(2000)).await; + // loop {} +} +``` + +## Code explain + +1. Load configuration from file or hard code the configuration + +We can hard code the configuration or load configuration from file. + +```rust +// let file = File::open("config.json").expect("Failed to open config file"); +// let reader = BufReader::new(file); +// let configs: Vec = +// serde_json::from_reader(reader).expect("Failed to parse config file"); + +let configs = vec![ + Config { interval: 10, url: "http://www.baidu.com".to_string() }, + Config { interval: 10, url: "http://www.qq.com".to_string() }, +]; +``` + +2. Create a task for each URL and spawn it + +```rust +for config in configs { + tokio::spawn(async move { + println!("spawn check future ..."); + check_url(config.clone()).await; + }); +} +``` + +3. Keep the program running so that other tasks can continue to run + +```rust +time::sleep(Duration::from_secs(2000)).await; +// loop {} // Keep the program running so that other tasks can continue to run +``` diff --git a/src/rust/tokio/tokio-codec.md b/src/rust/tokio/tokio-codec.md new file mode 100644 index 0000000..35dab23 --- /dev/null +++ b/src/rust/tokio/tokio-codec.md @@ -0,0 +1,1184 @@ +# Tokio Codec + +- [Intro](#intro) + - [EchoCodec](#echocodec) + - [Echo using io::Copy](#echo-using-iocopy) +- [Stream Sink trait](#stream-sink-trait) + +# Intro + +今天来讲讲 `tokio` 的 `codec`。 + +顾名思义,`codec` 是一个编码解码器,用于将原始字节解码为 `rust` 的数据类型。 + +首先,我们来看看 `codec` 的基本用法。 + +## EchoCodec + +我们首先来看一个简单的例子,我们将 `tokio` 中的 `TcpStream` 进行编码和解码。 + +实现 `Decoder` 和 `Encoder` 这两个 `trait` 即可拥有编解码的功能。 + +在实现这两个 `trait` 之前,我们首先定义错误类型,这里使用 `enum ConnectionError`,使用 `enum` 也是最常见的定义错误类型的方式。 + +为什么要先定义错误类型呢?因为 `Decoder` 和 `Encoder` 这两个 `trait` 都定义了一个叫做 `Error` 的关联类型(`associated type`),所以为了实现这两个 `trait`,我们也需要定义一个错误类型。 + +下面是 `tokio_util::codec` 这个包(`package`)里的 `Decoder` 和 `Encoder` 的定义。 + +`Decoder` `trait` 的定义: + +```rust +/// Decoding of frames via buffers. +/// +/// This trait is used when constructing an instance of [`Framed`] or +/// [`FramedRead`]. An implementation of `Decoder` takes a byte stream that has +/// already been buffered in `src` and decodes the data into a stream of +/// `Self::Item` frames. +/// +/// Implementations are able to track state on `self`, which enables +/// implementing stateful streaming parsers. In many cases, though, this type +/// will simply be a unit struct (e.g. `struct HttpDecoder`). +/// +/// For some underlying data-sources, namely files and FIFOs, +/// it's possible to temporarily read 0 bytes by reaching EOF. +/// +/// In these cases `decode_eof` will be called until it signals +/// fullfillment of all closing frames by returning `Ok(None)`. +/// After that, repeated attempts to read from the [`Framed`] or [`FramedRead`] +/// will not invoke `decode` or `decode_eof` again, until data can be read +/// during a retry. +/// +/// It is up to the Decoder to keep track of a restart after an EOF, +/// and to decide how to handle such an event by, for example, +/// allowing frames to cross EOF boundaries, re-emitting opening frames, or +/// resetting the entire internal state. +/// +/// [`Framed`]: crate::codec::Framed +/// [`FramedRead`]: crate::codec::FramedRead +pub trait Decoder { + /// The type of decoded frames. + type Item; + + /// The type of unrecoverable frame decoding errors. + /// + /// If an individual message is ill-formed but can be ignored without + /// interfering with the processing of future messages, it may be more + /// useful to report the failure as an `Item`. + /// + /// `From` is required in the interest of making `Error` suitable + /// for returning directly from a [`FramedRead`], and to enable the default + /// implementation of `decode_eof` to yield an `io::Error` when the decoder + /// fails to consume all available data. + /// + /// Note that implementors of this trait can simply indicate `type Error = + /// io::Error` to use I/O errors as this type. + /// + /// [`FramedRead`]: crate::codec::FramedRead + type Error: From; + + /// Attempts to decode a frame from the provided buffer of bytes. + /// + /// This method is called by [`FramedRead`] whenever bytes are ready to be + /// parsed. The provided buffer of bytes is what's been read so far, and + /// this instance of `Decode` can determine whether an entire frame is in + /// the buffer and is ready to be returned. + /// + /// If an entire frame is available, then this instance will remove those + /// bytes from the buffer provided and return them as a decoded + /// frame. Note that removing bytes from the provided buffer doesn't always + /// necessarily copy the bytes, so this should be an efficient operation in + /// most circumstances. + /// + /// If the bytes look valid, but a frame isn't fully available yet, then + /// `Ok(None)` is returned. This indicates to the [`Framed`] instance that + /// it needs to read some more bytes before calling this method again. + /// + /// Note that the bytes provided may be empty. If a previous call to + /// `decode` consumed all the bytes in the buffer then `decode` will be + /// called again until it returns `Ok(None)`, indicating that more bytes need to + /// be read. + /// + /// Finally, if the bytes in the buffer are malformed then an error is + /// returned indicating why. This informs [`Framed`] that the stream is now + /// corrupt and should be terminated. + /// + /// [`Framed`]: crate::codec::Framed + /// [`FramedRead`]: crate::codec::FramedRead + /// + /// # Buffer management + /// + /// Before returning from the function, implementations should ensure that + /// the buffer has appropriate capacity in anticipation of future calls to + /// `decode`. Failing to do so leads to inefficiency. + /// + /// For example, if frames have a fixed length, or if the length of the + /// current frame is known from a header, a possible buffer management + /// strategy is: + /// + /// # use std::io; + /// # + /// # use bytes::BytesMut; + /// # use tokio_util::codec::Decoder; + /// # + /// # struct MyCodec; + /// # + /// impl Decoder for MyCodec { + /// // ... + /// # type Item = BytesMut; + /// # type Error = io::Error; + /// + /// fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + /// // ... + /// + /// // Reserve enough to complete decoding of the current frame. + /// let current_frame_len: usize = 1000; // Example. + /// // And to start decoding the next frame. + /// let next_frame_header_len: usize = 10; // Example. + /// src.reserve(current_frame_len + next_frame_header_len); + /// + /// return Ok(None); + /// } + /// } + /// + /// An optimal buffer management strategy minimizes reallocations and + /// over-allocations. + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error>; + + /// A default method available to be called when there are no more bytes + /// available to be read from the underlying I/O. + /// + /// This method defaults to calling `decode` and returns an error if + /// `Ok(None)` is returned while there is unconsumed data in `buf`. + /// Typically this doesn't need to be implemented unless the framing + /// protocol differs near the end of the stream, or if you need to construct + /// frames _across_ eof boundaries on sources that can be resumed. + /// + /// Note that the `buf` argument may be empty. If a previous call to + /// `decode_eof` consumed all the bytes in the buffer, `decode_eof` will be + /// called again until it returns `None`, indicating that there are no more + /// frames to yield. This behavior enables returning finalization frames + /// that may not be based on inbound data. + /// + /// Once `None` has been returned, `decode_eof` won't be called again until + /// an attempt to resume the stream has been made, where the underlying stream + /// actually returned more data. + fn decode_eof(&mut self, buf: &mut BytesMut) -> Result, Self::Error> { + match self.decode(buf)? { + Some(frame) => Ok(Some(frame)), + None => { + if buf.is_empty() { + Ok(None) + } else { + Err(io::Error::new(io::ErrorKind::Other, "bytes remaining on stream").into()) + } + } + } + } + + /// Provides a [`Stream`] and [`Sink`] interface for reading and writing to this + /// `Io` object, using `Decode` and `Encode` to read and write the raw data. + /// + /// Raw I/O objects work with byte sequences, but higher-level code usually + /// wants to batch these into meaningful chunks, called "frames". This + /// method layers framing on top of an I/O object, by using the `Codec` + /// traits to handle encoding and decoding of messages frames. Note that + /// the incoming and outgoing frame types may be distinct. + /// + /// This function returns a *single* object that is both `Stream` and + /// `Sink`; grouping this into a single object is often useful for layering + /// things like gzip or TLS, which require both read and write access to the + /// underlying object. + /// + /// If you want to work more directly with the streams and sink, consider + /// calling `split` on the [`Framed`] returned by this method, which will + /// break them into separate objects, allowing them to interact more easily. + /// + /// [`Stream`]: futures_core::Stream + /// [`Sink`]: futures_sink::Sink + /// [`Framed`]: crate::codec::Framed + fn framed(self, io: T) -> Framed + where + Self: Sized, + { + Framed::new(io, self) + } +} +``` + +`Encoder` `trait` 的定义: + +```rust +/// Trait of helper objects to write out messages as bytes, for use with +/// [`FramedWrite`]. +/// +/// [`FramedWrite`]: crate::codec::FramedWrite +pub trait Encoder { + /// The type of encoding errors. + /// + /// [`FramedWrite`] requires `Encoder`s errors to implement `From` + /// in the interest letting it return `Error`s directly. + /// + /// [`FramedWrite`]: crate::codec::FramedWrite + type Error: From; + + /// Encodes a frame into the buffer provided. + /// + /// This method will encode `item` into the byte buffer provided by `dst`. + /// The `dst` provided is an internal buffer of the [`FramedWrite`] instance and + /// will be written out when possible. + /// + /// [`FramedWrite`]: crate::codec::FramedWrite + fn encode(&mut self, item: Item, dst: &mut BytesMut) -> Result<(), Self::Error>; +} +``` + +由于还不知道未来会有几种类型的错误,我们先随意定义两个: `Disconnected` 和 `Io(io::IoError)`,分别代表 `网络连接出错(断开)`以及`读取 socket 时发生的 io 错误`,当然实际场景的错误更加复杂和多样。 + +```rust +// 因为 codecs 的 Encoder trait 有个 associate type ,所以需要 Error 定义 +#[derive(Debug)] +pub enum ConnectionError { + Io(io::Error), + Disconnected, +} + +impl From for ConnectionError { + fn from(err: io::Error) -> Self { + ConnectionError::Io(err) + } +} +``` + +其次定义 `EchoCodec`,它实现了 `Decoder` 和 `Encoder` `trait`,其中 `Decoder` 和 `Encoder` 的 `associated type` 都是 `ConnectionError`。 + +实现 `From` 的原因是 `Encoder` 的关联类型的类型约束: `type Error: From`,即我们必须能够将 `io::Error` 转换为 `ConnectionError`。 + +接下来我们定义需要被编码的消息类型 `Message`,它是一个 `String` 类型,并为此实现 `Encoder` 和 `Decoder` `trait`。 + +被编码(`Encode`)的意思是,将 `Message` 类型转换为 `BytesMut`,然后写入到 `TcpStream` 中。 +被解码(`Decode`)的意思是,从 `FramedRead` 中读取 `BytesMut`,然后解码为 `Message` 供应用程序使用。 + +```rust +use tokio::codec::{Decoder, Encoder}; + +type Message = String; + +struct EchoCodec; + +// 给 EchoCodec 实现 Encoder trait +impl Encoder for EchoCodec { + type Error = ConnectionError; + + fn encode( + &mut self, item: Message, dst: &mut BytesMut, + ) -> Result<(), Self::Error> { + // 将 Message 写入 dst + dst.extend(item.as_bytes()); + Ok(()) + } +} + +// 给 EchoCodec 实现 Decoder trait +impl Decoder for EchoCodec { + type Item = Message; + + type Error = ConnectionError; + + fn decode( + &mut self, src: &mut BytesMut, + ) -> Result, Self::Error> { + // 将 src 中的数据转换为 String + if src.is_empty() { + return Ok(None); + } + // 将 src 中的数据移除 + let data = src.split(); + let data = String::from_utf8_lossy(&data[..]).to_string(); + + // 将 line 转换为 Message + Ok(Some(data)) + } +} +``` + +上面可以看出,`encode` 方法就是将 `Message` 转换为 `bytes` 并写入 `BytesMut`(通过 `BytesMut` 的 `extend` 方法),而 `decode` 方法就是将 `BytesMut` 转换为 `Message`。 + +最后,在 `main` 函数里是这么使用的: + +```rust +#[tokio::main] +async fn main() -> Result<(), Box> { + // start listening on 50007 + let listener = TcpListener::bind("127.0.0.1:50007").await?; + println!("echo server started!"); + + loop { + let (socket, addr) = listener.accept().await?; + + println!("accepted connection from: {}", addr); + + tokio::spawn(async move { + let codec = EchoCodec {}; + let mut conn = codec.framed(socket); + while let Some(message) = conn.next().await { + if let Ok(message) = message { + println!("received: {:?}", message); + conn.send(message).await.unwrap(); + } + } + }); + } +} +``` + +值得注意的是,`codec` 的 `framed` 方法(`codec.framed(socket)`)将 `TcpStream` 转换为 `Framed`,这个 `Framed` 就是实现了 `tokio` 中的 `Stream` 和 `Sink` 这两个 `trait`,因而具有了接收(通过 `Stream`)和发送(通过 `Sink`)数据的功能,关于这两个 `trait`,后面会提到。 + +上述 `framed` 方法是 `Decoder` `trait` 的方法,它第一个参数是 `TcpStream`,第二个参数是 `EchoCodec`,这个 `EchoCodec` 就是我们定义的 `EchoCodec`。 + +`Decoder::framed` 方法的定义如下: + +```rust +fn framed(self, codec: U) -> Framed +where + T: AsyncRead + AsyncWrite, + U: Decoder + Encoder, +{ + Framed::new(self, codec) +} +``` + +`Framed::new` 方法创建一个 `Framed` 实例,将 `TcpStream` 和 `EchoCodec` 保存在 `FramedImpl` 中。 + +`Framed` struct 的定义以及 `new` 方法的定义: + +```rust +pin_project! { + /// A unified [`Stream`] and [`Sink`] interface to an underlying I/O object, using + /// the `Encoder` and `Decoder` traits to encode and decode frames. + /// + /// You can create a `Framed` instance by using the [`Decoder::framed`] adapter, or + /// by using the `new` function seen below. + /// [`Stream`]: futures_core::Stream + /// [`Sink`]: futures_sink::Sink + /// [`AsyncRead`]: tokio::io::AsyncRead + /// [`Decoder::framed`]: crate::codec::Decoder::framed() + pub struct Framed { + #[pin] + inner: FramedImpl + } +} + +impl Framed +where + T: AsyncRead + AsyncWrite, +{ + /// Provides a [`Stream`] and [`Sink`] interface for reading and writing to this + /// I/O object, using [`Decoder`] and [`Encoder`] to read and write the raw data. + /// + /// Raw I/O objects work with byte sequences, but higher-level code usually + /// wants to batch these into meaningful chunks, called "frames". This + /// method layers framing on top of an I/O object, by using the codec + /// traits to handle encoding and decoding of messages frames. Note that + /// the incoming and outgoing frame types may be distinct. + /// + /// This function returns a *single* object that is both [`Stream`] and + /// [`Sink`]; grouping this into a single object is often useful for layering + /// things like gzip or TLS, which require both read and write access to the + /// underlying object. + /// + /// If you want to work more directly with the streams and sink, consider + /// calling [`split`] on the `Framed` returned by this method, which will + /// break them into separate objects, allowing them to interact more easily. + /// + /// Note that, for some byte sources, the stream can be resumed after an EOF + /// by reading from it, even after it has returned `None`. Repeated attempts + /// to do so, without new data available, continue to return `None` without + /// creating more (closing) frames. + /// + /// [`Stream`]: futures_core::Stream + /// [`Sink`]: futures_sink::Sink + /// [`Decode`]: crate::codec::Decoder + /// [`Encoder`]: crate::codec::Encoder + /// [`split`]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html#method.split + pub fn new(inner: T, codec: U) -> Framed { + Framed { + inner: FramedImpl { + inner, + codec, + state: Default::default(), + }, + } + } +} +``` + +`FramedImpl` 除了保存了 `TcpStream` 和 `EchoCodec`,它还保存了一个 `State`,这个 `State` 是一个 `RWFrames` 实例,相当于一个缓冲区。 + +`FramedImpl` struct 的定义: + +```rust +pin_project! { + #[derive(Debug)] + pub(crate) struct FramedImpl { + #[pin] + pub(crate) inner: T, + pub(crate) state: State, + pub(crate) codec: U, + } +} +``` + +`RWFrames` 、`ReadFrame` 和 `WriteFrame` 的定义。 + +```rust +#[derive(Debug)] +pub(crate) struct ReadFrame { + pub(crate) eof: bool, + pub(crate) is_readable: bool, + pub(crate) buffer: BytesMut, + pub(crate) has_errored: bool, +} + +pub(crate) struct WriteFrame { + pub(crate) buffer: BytesMut, +} + +#[derive(Default)] +pub(crate) struct RWFrames { + pub(crate) read: ReadFrame, + pub(crate) write: WriteFrame, +} +``` + +`RWFrames` 实现了 `Borrow` 和 `BorrowMut` 这两个 `trait`,能分别返回 `ReadFrame` 和 `WriteFrame` 用来作为读写数据的缓冲。 + +```rust +impl Borrow for RWFrames { + fn borrow(&self) -> &ReadFrame { + &self.read + } +} +impl BorrowMut for RWFrames { + fn borrow_mut(&mut self) -> &mut ReadFrame { + &mut self.read + } +} +impl Borrow for RWFrames { + fn borrow(&self) -> &WriteFrame { + &self.write + } +} +impl BorrowMut for RWFrames { + fn borrow_mut(&mut self) -> &mut WriteFrame { + &mut self.write + } +} +``` + +`RWFrames` 实现 `Borrow` `BorrowMut` 也比较有意思,当需要从 `Stream` `读` 数据(这里指异步读取 `AsyncRead`)的时候,会调用 `BorrowMut` 方法,返回内部的 `ReadFrame` 的引用,作为读数据的缓冲。当需要向 `Sink` `写` 数据(这里指异步写入 `AsyncWrite`)的时候,会调用 `BorrowMut` 方法,返回内部的 `WriteFrame` 的引用,作为写数据的缓冲。 + +而 `FramedImpl` 实现了 `Stream` 和 `Sink` 这两个 `trait`。`Stream` 代表读数据,`Sink` 代表写数据。实现 `Stream` 时,`FramedImpl` 的泛型参数的约束是 `T: AsyncRead` 和 `R: BorrowMut`,表示 `FramedImpl::inner` 只需满足 `AsyncRead`,而且读取操作时会用到 `ReadFrame`。 + +实现 `Sink` 时,`FramedImpl` 的泛型参数的约束是 `T: AsyncWrite` 和 `R: BorrowMut`。实现 `Sink` 时,表示 `FramedImpl::inner` 只需满足 `AsyncWrite`,而且写入操作时会用到 `WriteFrame`。 + +另外,比较有趣的是,`FramedImpl` 实现 `Stream` 时,`poll_next` 方法有个状态机,体现了读取数据流时复杂的流程。 + +```rust +impl Stream for FramedImpl +where + T: AsyncRead, + U: Decoder, + R: BorrowMut, +{ + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + use crate::util::poll_read_buf; + + let mut pinned = self.project(); + let state: &mut ReadFrame = pinned.state.borrow_mut(); + // The following loops implements a state machine with each state corresponding + // to a combination of the `is_readable` and `eof` flags. States persist across + // loop entries and most state transitions occur with a return. + // + // The initial state is `reading`. + // + // | state | eof | is_readable | has_errored | + // |---------|-------|-------------|-------------| + // | reading | false | false | false | + // | framing | false | true | false | + // | pausing | true | true | false | + // | paused | true | false | false | + // | errored | | | true | + // `decode_eof` returns Err + // ┌────────────────────────────────────────────────────────┐ + // `decode_eof` returns │ │ + // `Ok(Some)` │ │ + // ┌─────┐ │ `decode_eof` returns After returning │ + // Read 0 bytes ├─────▼──┴┐ `Ok(None)` ┌────────┐ ◄───┐ `None` ┌───▼─────┐ + // ┌────────────────►│ Pausing ├───────────────────────►│ Paused ├─┐ └───────────┤ Errored │ + // │ └─────────┘ └─┬──▲───┘ │ └───▲───▲─┘ + // Pending read │ │ │ │ │ │ + // ┌──────┐ │ `decode` returns `Some` │ └─────┘ │ │ + // │ │ │ ┌──────┐ │ Pending │ │ + // │ ┌────▼──┴─┐ Read n>0 bytes ┌┴──────▼─┐ read n>0 bytes │ read │ │ + // └─┤ Reading ├───────────────►│ Framing │◄────────────────────────┘ │ │ + // └──┬─▲────┘ └─────┬──┬┘ │ │ + // │ │ │ │ `decode` returns Err │ │ + // │ └───decode` returns `None`──┘ └───────────────────────────────────────────────────────┘ │ + // │ read returns Err │ + // └────────────────────────────────────────────────────────────────────────────────────────────┘ + loop { + // too long, omit + } + } +``` + +`FramedImpl` 实现了 `Stream`,我们就能够从它那里读取数据了。 + +读取数据的过程是通过 `StreamExt::next` 方法实现的,它是对 `Stream` `trait` 的扩展,提供了很多实用方法,其中 `next` 就是其中一个。 + +`StreamExt::next` 方法的定义: + +```rust +/// An extension trait for `Stream`s that provides a variety of convenient +/// combinator functions. +pub trait StreamExt: Stream { + /// Creates a future that resolves to the next item in the stream. + /// + /// Note that because `next` doesn't take ownership over the stream, + /// the [`Stream`] type must be [`Unpin`]. If you want to use `next` with a + /// [`!Unpin`](Unpin) stream, you'll first have to pin the stream. This can + /// be done by boxing the stream using [`Box::pin`] or + /// pinning it to the stack using the `pin_mut!` macro from the `pin_utils` + /// crate. + /// + /// # Examples + /// + /// # futures::executor::block_on(async { + /// use futures::stream::{self, StreamExt}; + /// + /// let mut stream = stream::iter(1..=3); + /// + /// assert_eq!(stream.next().await, Some(1)); + /// assert_eq!(stream.next().await, Some(2)); + /// assert_eq!(stream.next().await, Some(3)); + /// assert_eq!(stream.next().await, None); + /// # }); + fn next(&mut self) -> Next<'_, Self> + where + Self: Unpin, + { + assert_future::, _>(Next::new(self)) + } + // other methods... +} +``` + +`StreamExt::next` 方法创建一个对自身的引用,并且返回一个 `Next` 对象,这个对象实现了 `Future` `trait`,所以我们可以通过 `await` 来读取数据。 + +`Next` struct 的定义: + +```rust +/// Future for the [`next`](super::StreamExt::next) method. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Next<'a, St: ?Sized> { + stream: &'a mut St, +} + +impl Unpin for Next<'_, St> {} + +impl<'a, St: ?Sized + Stream + Unpin> Next<'a, St> { + pub(super) fn new(stream: &'a mut St) -> Self { + Self { stream } + } +} + +impl FusedFuture for Next<'_, St> { + fn is_terminated(&self) -> bool { + self.stream.is_terminated() + } +} + +impl Future for Next<'_, St> { + type Output = Option; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.stream.poll_next_unpin(cx) + } +} +``` + +在得知 `FramedImpl` 如何读取数据之后,那么 `FramedImpl` 是如何实现向 `Sink` 写入数据的呢? + +`FramedImpl` 实现了 `Sink` `trait`,可以看到主要是调用了 `FramedImpl::poll_flush` 方法将 `Encoder` 编码的数据通过字节流发送出去。 + +```rust +impl Sink for FramedImpl +where + T: AsyncWrite, + U: Encoder, + U::Error: From, + W: BorrowMut, +{ + type Error = U::Error; + + fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if self.state.borrow().buffer.len() >= BACKPRESSURE_BOUNDARY { + self.as_mut().poll_flush(cx) + } else { + Poll::Ready(Ok(())) + } + } + + fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> { + let pinned = self.project(); + pinned + .codec + .encode(item, &mut pinned.state.borrow_mut().buffer)?; + Ok(()) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + use crate::util::poll_write_buf; + trace!("flushing framed transport"); + let mut pinned = self.project(); + + while !pinned.state.borrow_mut().buffer.is_empty() { + let WriteFrame { buffer } = pinned.state.borrow_mut(); + trace!("writing; remaining={}", buffer.len()); + + let n = ready!(poll_write_buf(pinned.inner.as_mut(), cx, buffer))?; + + if n == 0 { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::WriteZero, + "failed to \ + write frame to transport", + ) + .into())); + } + } + + // Try flushing the underlying IO + ready!(pinned.inner.poll_flush(cx))?; + + trace!("framed transport flushed"); + Poll::Ready(Ok(())) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + ready!(self.as_mut().poll_flush(cx))?; + ready!(self.project().inner.poll_shutdown(cx))?; + + Poll::Ready(Ok(())) + } +} +``` + +而我们能通过 `send` 方法(参看 `main` 函数中的 `conn.send(message).await.unwrap();`)将编码的 `Message` 发送出去,是因为 `SinkExt` 是对 `Sink` `trait` 的扩展,它提供了 `send` 方法。 + +```rust +impl SinkExt for T where T: Sink {} + +/// An extension trait for `Sink`s that provides a variety of convenient +/// combinator functions. +pub trait SinkExt: Sink { + /// A future that completes after the given item has been fully processed + /// into the sink, including flushing. + /// + /// Note that, **because of the flushing requirement, it is usually better + /// to batch together items to send via `feed` or `send_all`, + /// rather than flushing between each item.** + fn send(&mut self, item: Item) -> Send<'_, Self, Item> + where + Self: Unpin, + { + assert_future::, _>(Send::new(self, item)) + } + + // other methods... +} +``` + +这个方法返回一个 `Send` struct,它是对 `Feed` 的一个简单 wrapper,它的作用是将 `item` 发送出去,发送功能交给 `Feed::sink_pin_mut::poll_flush` 来实现。 + +```rust +/// Future for the [`send`](super::SinkExt::send) method. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Send<'a, Si: ?Sized, Item> { + feed: Feed<'a, Si, Item>, +} + +// Pinning is never projected to children +impl Unpin for Send<'_, Si, Item> {} + +impl<'a, Si: Sink + Unpin + ?Sized, Item> Send<'a, Si, Item> { + pub(super) fn new(sink: &'a mut Si, item: Item) -> Self { + Self { feed: Feed::new(sink, item) } + } +} + +impl + Unpin + ?Sized, Item> Future for Send<'_, Si, Item> { + type Output = Result<(), Si::Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = &mut *self; + + if this.feed.is_item_pending() { + ready!(Pin::new(&mut this.feed).poll(cx))?; + debug_assert!(!this.feed.is_item_pending()); + } + + // we're done sending the item, but want to block on flushing the + // sink + ready!(this.feed.sink_pin_mut().poll_flush(cx))?; + + Poll::Ready(Ok(())) + } +} +``` + +这里是 `Feed` struct 的定义: + +```rust +/// Future for the [`feed`](super::SinkExt::feed) method. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Feed<'a, Si: ?Sized, Item> { + sink: &'a mut Si, + item: Option, +} + +// Pinning is never projected to children +impl Unpin for Feed<'_, Si, Item> {} + +impl<'a, Si: Sink + Unpin + ?Sized, Item> Feed<'a, Si, Item> { + pub(super) fn new(sink: &'a mut Si, item: Item) -> Self { + Feed { sink, item: Some(item) } + } + + pub(super) fn sink_pin_mut(&mut self) -> Pin<&mut Si> { + Pin::new(self.sink) + } + + pub(super) fn is_item_pending(&self) -> bool { + self.item.is_some() + } +} + +impl + Unpin + ?Sized, Item> Future for Feed<'_, Si, Item> { + type Output = Result<(), Si::Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + let mut sink = Pin::new(&mut this.sink); + ready!(sink.as_mut().poll_ready(cx))?; + let item = this.item.take().expect("polled Feed after completion"); + sink.as_mut().start_send(item)?; + Poll::Ready(Ok(())) + } +} +``` + +`Feed` 实现了 `Future` `trait`,其 `poll` 方法首先调用 `poll_ready` 方法,如果 `poll_ready` 返回 `Ready`,则调用 `start_send` 方法,将 `item` 发送出去,如果 `start_send` 返回 `Ready`,则返回 `Ready`,否则继续调用 `poll_ready` 方法。 + +`poll_ready` 存在的意义是对是否能够发送 `item` 做出判断,如果不能发送,则需要等待(`poll_ready` 返回 `Poll::Pending` 等待被唤醒,具体实现是通过调用 `cx.waker().wake_by_ref()` 将异步任务注册,等待下一次被调度,`poll_ready` 的文档说明了这个过程,见下面 👇),直到能够发送。举个例子,在 `FramedImpl` 实现 `Sink` `trait` 时,采用了底层缓冲区(`WriteFrame`)的方式来存储待发送的数据,如果缓冲区满了,则调用 `poll_flush` 方法,否则表示可以开始发送数据(调用 `start_send` 方法)。 + +`FramedImpl::poll_ready` 方法的实现如下: + +```rust +/// Attempts to prepare the `Sink` to receive a value. +/// +/// This method must be called and return `Poll::Ready(Ok(()))` prior to +/// each call to `start_send`. +/// +/// This method returns `Poll::Ready` once the underlying sink is ready to +/// receive data. If this method returns `Poll::Pending`, the current task +/// is registered to be notified (via `cx.waker().wake_by_ref()`) when `poll_ready` +/// should be called again. +/// +/// In most cases, if the sink encounters an error, the sink will +/// permanently be unable to receive items. +fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if self.state.borrow().buffer.len() >= BACKPRESSURE_BOUNDARY { + self.as_mut().poll_flush(cx) + } else { + Poll::Ready(Ok(())) + } +} +``` + +通过分析 `Feed` 的 `poll` 方法,我们得知数据最终是如何发送出去的了。 + +至于待发送的数据何时被编码,我们可以看到是在 `FramedImpl::start_send` 方法来做的。 + +`FramedImpl::start_end` 方法的实现如下: + +```rust +fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> { + let pinned = self.project(); + pinned + .codec + .encode(item, &mut pinned.state.borrow_mut().buffer)?; + Ok(()) +} +``` + +所以,我们能通过 `next` 来从数据流中接收并解析成 `Message` 结构,然后又通过 `send` 方法来将接收到的数据发送出去了。 + +`next` 接收数据 `send` 发送数据: + +```rust +while let Some(message) = conn.next().await { + if let Ok(message) = message { + println!("received: {:?}", message); + conn.send(message).await.unwrap(); + } +} +``` + +总结一下就是: + +- `SinkExt` 提供了 `send` 方法,用于将接收到的数据发送出去 +- `SinkExt::send` 方法通过 `Send::new` 返回一个实现了 `Future` 的 `Send` struct +- `Send` 内部采用 `Feed` 实现,目的是防止重复发送(将带发送的 `Item` 放入 `Option`,在 `poll` 被调用前检查是否已经被编码发送出去,如果已经被编码并发送,则 `Option` 为 `None`),`Feed` 也实现了 `Future` `trait` +- `Send` 方法首先检查 `Feed` 的 `is_item_pending` 方法,如果 `Feed` 的 `item` 为 `None`,则表示 `Feed` 已经被编码并发送出去,如果 `Feed` 的 `item` 为 `Some`,则表示 `Feed` 还未被编码并发送出去,需要调用 `Feed` 的 `poll` 方法。 +- `Feed::poll` 方法完成发送逻辑 + - 调用 `poll_ready` 判断是否可以发送,缓冲区 `BACKPRESSURE_BOUNDARY` 大小为 8k,满了则无法发送 + - 调用 `self.item.take` 将待发送的 `Item` 取出 + - 调用 `start_send` 对 `Item` 进行编码 +- `Send` 最后调用 `poll_flush` (此时是 `FramedImpl::poll_flush`)刷新写缓冲区 + +客户端测试: + +首先运行 server 端: + +```rust +echo server started! +accepted connection from: 127.0.0.1:60105 +received: "1\r\n" +received: "2\r\n" +received: "3\r\n" +received: "44\r\n" +received: "55\r\n" +received: "66\r\n" +received: "777\r\n" +received: "888\r\n" +received: "999\r\n" +``` + +其次使用 `telnet` 来连接 `server` 端,并输入数字,然后按回车键,这些数字会被转换成字符串,然后会被发送到 `server` 端。 + +客户端的连接: + +``` +telnet localhost 50007 +Trying 127.0.0.1... +Connected to localhost. +Escape character is '^]'. +1 +1 +2 +2 +3 +3 +44 +44 +55 +55 +66 +66 +777 +777 +888 +888 +999 +999 +``` + +可以看到,`server` 端接收到的数据是 `1\r\n`, `2\r\n`, `3\r\n`, `44\r\n`, `55\r\n`, `66\r\n`, `777\r\n`, `888\r\n`, `999\r\n`,成功接受到来自 `client` 端的数据。 + +## Echo using io::Copy + +手动实现 `EchoCodec` 比较繁琐,为了方便,我们可以使用 `io::copy` 来实现 `EchoCodec` 的功能,它的实现如下: + +首先,`socket.split()` 将 `socket` 分成两个部分,一个是接收数据(这个在 `tokio` 里叫做 `ReadHalf`),一个是发送数据(这个在 `tokio` 里叫做 `WriteHalf`)。`io::copy` 将接收数据(`ReadHalf`)的部分拷贝到发送数据(`WritHalf`)的部分,这样就实现了数据的双向传输。 + +```rust +// 使用 io::copy 自动拷贝数据,需要调用 tokio::io::split 分割成 reader 和 writer +let (mut rd, mut wr) = socket.split(); +if io::copy(&mut rd, &mut wr).await.is_err() { + eprintln!("failed to copy"); +} +``` + +完整的实现如下: + +```rust +#[tokio::main] +async fn main() -> Result<(), Box> { + // start listening on 50007 + let listener = TcpListener::bind("127.0.0.1:50007").await?; + println!("echo server started!"); + + loop { + let (mut socket, addr) = listener.accept().await?; + + println!("accepted connection from: {}", addr); + + tokio::spawn(async move { + // 方法1: + // 使用 io::copy 自动拷贝数据,需要调用 tokio::io::split 分割成 reader 和 writer + let (mut rd, mut wr) = socket.split(); + if io::copy(&mut rd, &mut wr).await.is_err() { + eprintln!("failed to copy"); + } + }); + } + Ok(()) +} +``` + +同样的,我们用客户端来进行测试: + +首先运行 server 端: + +```rust +echo server started! +accepted connection from: 127.0.0.1:60205 +received: "1\r\n" +received: "2\r\n" +received: "3\r\n" +received: "44\r\n" +received: "55\r\n" +received: "66\r\n" +received: "777\r\n" +received: "888\r\n" +received: "999\r\n" +``` + +其次使用 `telnet` 来连接 `server` 端,并输入数字,然后按回车键,这些数字会被转换成字符串,然后会被发送到 `server` 端。 + +客户端的连接: + +``` +telnet localhost 50007 +Trying 127.0.0.1... +Connected to localhost. +Escape character is '^]'. +1 +1 +2 +2 +3 +3 +44 +44 +55 +55 +66 +66 +777 +777 +888 +888 +999 +999 +``` + +可以看到,利用 `io::copy` 和手动实现 `EchoCodec` 的输出一致。 + +# Stream Sink trait + +最后,附赠一下 `Stream` 和 `Sink` `trait` 的定义。 + +实现了 `tokio` 中的 `Stream` 和 `Sink` 就能从数据流(如 `TcpStream` 或 `File`)中获取数据,并且能够将数据写回到数据流中。 + +`Stream` `trait` 的定义: + +```rust +/// A stream of values produced asynchronously. +/// +/// If `Future` is an asynchronous version of `T`, then `Stream` is an asynchronous version of `Iterator`. A stream +/// represents a sequence of value-producing events that occur asynchronously to +/// the caller. +/// +/// The trait is modeled after `Future`, but allows `poll_next` to be called +/// even after a value has been produced, yielding `None` once the stream has +/// been fully exhausted. +#[must_use = "streams do nothing unless polled"] +pub trait Stream { + /// Values yielded by the stream. + type Item; + + /// Attempt to pull out the next value of this stream, registering the + /// current task for wakeup if the value is not yet available, and returning + /// `None` if the stream is exhausted. + /// + /// # Return value + /// + /// There are several possible return values, each indicating a distinct + /// stream state: + /// + /// - `Poll::Pending` means that this stream's next value is not ready + /// yet. Implementations will ensure that the current task will be notified + /// when the next value may be ready. + /// + /// - `Poll::Ready(Some(val))` means that the stream has successfully + /// produced a value, `val`, and may produce further values on subsequent + /// `poll_next` calls. + /// + /// - `Poll::Ready(None)` means that the stream has terminated, and + /// `poll_next` should not be invoked again. + /// + /// # Panics + /// + /// Once a stream has finished (returned `Ready(None)` from `poll_next`), calling its + /// `poll_next` method again may panic, block forever, or cause other kinds of + /// problems; the `Stream` trait places no requirements on the effects of + /// such a call. However, as the `poll_next` method is not marked `unsafe`, + /// Rust's usual rules apply: calls must never cause undefined behavior + /// (memory corruption, incorrect use of `unsafe` functions, or the like), + /// regardless of the stream's state. + /// + /// If this is difficult to guard against then the [`fuse`] adapter can be used + /// to ensure that `poll_next` always returns `Ready(None)` in subsequent + /// calls. + /// + /// [`fuse`]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html#method.fuse + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>; + + /// Returns the bounds on the remaining length of the stream. + /// + /// Specifically, `size_hint()` returns a tuple where the first element + /// is the lower bound, and the second element is the upper bound. + /// + /// The second half of the tuple that is returned is an [`Option`]`<`[`usize`]`>`. + /// A [`None`] here means that either there is no known upper bound, or the + /// upper bound is larger than [`usize`]. + /// + /// # Implementation notes + /// + /// It is not enforced that a stream implementation yields the declared + /// number of elements. A buggy stream may yield less than the lower bound + /// or more than the upper bound of elements. + /// + /// `size_hint()` is primarily intended to be used for optimizations such as + /// reserving space for the elements of the stream, but must not be + /// trusted to e.g., omit bounds checks in unsafe code. An incorrect + /// implementation of `size_hint()` should not lead to memory safety + /// violations. + /// + /// That said, the implementation should provide a correct estimation, + /// because otherwise it would be a violation of the trait's protocol. + /// + /// The default implementation returns `(0, `[`None`]`)` which is correct for any + /// stream. + #[inline] + fn size_hint(&self) -> (usize, Option) { + (0, None) + } +} +``` + +`Sink` `trait` 的定义: + +```rust +/// A `Sink` is a value into which other values can be sent, asynchronously. +/// +/// Basic examples of sinks include the sending side of: +/// +/// - Channels +/// - Sockets +/// - Pipes +/// +/// In addition to such "primitive" sinks, it's typical to layer additional +/// functionality, such as buffering, on top of an existing sink. +/// +/// Sending to a sink is "asynchronous" in the sense that the value may not be +/// sent in its entirety immediately. Instead, values are sent in a two-phase +/// way: first by initiating a send, and then by polling for completion. This +/// two-phase setup is analogous to buffered writing in synchronous code, where +/// writes often succeed immediately, but internally are buffered and are +/// *actually* written only upon flushing. +/// +/// In addition, the `Sink` may be *full*, in which case it is not even possible +/// to start the sending process. +/// +/// As with `Future` and `Stream`, the `Sink` trait is built from a few core +/// required methods, and a host of default methods for working in a +/// higher-level way. The `Sink::send_all` combinator is of particular +/// importance: you can use it to send an entire stream to a sink, which is +/// the simplest way to ultimately consume a stream. +#[must_use = "sinks do nothing unless polled"] +pub trait Sink { + /// The type of value produced by the sink when an error occurs. + type Error; + + /// Attempts to prepare the `Sink` to receive a value. + /// + /// This method must be called and return `Poll::Ready(Ok(()))` prior to + /// each call to `start_send`. + /// + /// This method returns `Poll::Ready` once the underlying sink is ready to + /// receive data. If this method returns `Poll::Pending`, the current task + /// is registered to be notified (via `cx.waker().wake_by_ref()`) when `poll_ready` + /// should be called again. + /// + /// In most cases, if the sink encounters an error, the sink will + /// permanently be unable to receive items. + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>; + + /// Begin the process of sending a value to the sink. + /// Each call to this function must be preceded by a successful call to + /// `poll_ready` which returned `Poll::Ready(Ok(()))`. + /// + /// As the name suggests, this method only *begins* the process of sending + /// the item. If the sink employs buffering, the item isn't fully processed + /// until the buffer is fully flushed. Since sinks are designed to work with + /// asynchronous I/O, the process of actually writing out the data to an + /// underlying object takes place asynchronously. **You *must* use + /// `poll_flush` or `poll_close` in order to guarantee completion of a + /// send**. + /// + /// Implementations of `poll_ready` and `start_send` will usually involve + /// flushing behind the scenes in order to make room for new messages. + /// It is only necessary to call `poll_flush` if you need to guarantee that + /// *all* of the items placed into the `Sink` have been sent. + /// + /// In most cases, if the sink encounters an error, the sink will + /// permanently be unable to receive items. + fn start_send(self: Pin<&mut Self>, item: Item) -> Result<(), Self::Error>; + + /// Flush any remaining output from this sink. + /// + /// Returns `Poll::Ready(Ok(()))` when no buffered items remain. If this + /// value is returned then it is guaranteed that all previous values sent + /// via `start_send` have been flushed. + /// + /// Returns `Poll::Pending` if there is more work left to do, in which + /// case the current task is scheduled (via `cx.waker().wake_by_ref()`) to wake up when + /// `poll_flush` should be called again. + /// + /// In most cases, if the sink encounters an error, the sink will + /// permanently be unable to receive items. + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>; + + /// Flush any remaining output and close this sink, if necessary. + /// + /// Returns `Poll::Ready(Ok(()))` when no buffered items remain and the sink + /// has been successfully closed. + /// + /// Returns `Poll::Pending` if there is more work left to do, in which + /// case the current task is scheduled (via `cx.waker().wake_by_ref()`) to wake up when + /// `poll_close` should be called again. + /// + /// If this function encounters an error, the sink should be considered to + /// have failed permanently, and no more `Sink` methods should be called. + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>; +} +``` diff --git a/src/solana/README.md b/src/solana/README.md new file mode 100644 index 0000000..481331b --- /dev/null +++ b/src/solana/README.md @@ -0,0 +1 @@ +# Solana diff --git "a/src/solana/quickstart/\344\270\215\344\275\277\347\224\250Anchor\345\274\200\345\217\221solana\347\232\204program.md" "b/src/solana/quickstart/\344\270\215\344\275\277\347\224\250Anchor\345\274\200\345\217\221solana\347\232\204program.md" new file mode 100644 index 0000000..fa11fe6 --- /dev/null +++ "b/src/solana/quickstart/\344\270\215\344\275\277\347\224\250Anchor\345\274\200\345\217\221solana\347\232\204program.md" @@ -0,0 +1,1835 @@ +# 不使用 Anchor 开发 solana program + +- [初始化工程](#%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A5%E7%A8%8B) + - [使用 Cargo 初始化工程](#%E4%BD%BF%E7%94%A8-cargo-%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A5%E7%A8%8B) +- [编写代码](#%E7%BC%96%E5%86%99%E4%BB%A3%E7%A0%81) + - [程序入口 entrypoint](#%E7%A8%8B%E5%BA%8F%E5%85%A5%E5%8F%A3-entrypoint) + - [修改 process_instruction 函数的签名](#%E4%BF%AE%E6%94%B9-process_instruction-%E5%87%BD%E6%95%B0%E7%9A%84%E7%AD%BE%E5%90%8D) +- [构建程序](#%E6%9E%84%E5%BB%BA%E7%A8%8B%E5%BA%8F) + - [使用 cargo build-sbf 构建程序](#%E4%BD%BF%E7%94%A8-cargo-build-sbf-%E6%9E%84%E5%BB%BA%E7%A8%8B%E5%BA%8F) + - [解决 build-sbf 编译失败问题](#%E8%A7%A3%E5%86%B3-build-sbf-%E7%BC%96%E8%AF%91%E5%A4%B1%E8%B4%A5%E9%97%AE%E9%A2%98) +- [部署](#%E9%83%A8%E7%BD%B2) + - [回到 Anchor 工程验证部署失败源自版本的问题](#%E5%9B%9E%E5%88%B0-anchor-%E5%B7%A5%E7%A8%8B%E9%AA%8C%E8%AF%81%E9%83%A8%E7%BD%B2%E5%A4%B1%E8%B4%A5%E6%BA%90%E8%87%AA%E7%89%88%E6%9C%AC%E7%9A%84%E9%97%AE%E9%A2%98) + - [再回来部署我们的 hello_world 工程](#%E5%86%8D%E5%9B%9E%E6%9D%A5%E9%83%A8%E7%BD%B2%E6%88%91%E4%BB%AC%E7%9A%84-hello_world-%E5%B7%A5%E7%A8%8B) + - [localnet 部署](#localnet-%E9%83%A8%E7%BD%B2) + - [devnet 部署](#devnet-%E9%83%A8%E7%BD%B2) +- [Tips](#tips) + - [Tip 1: solana cli 的版本和 Cargo.toml 里的版本保持一致](#tip-1-solana-cli-%E7%9A%84%E7%89%88%E6%9C%AC%E5%92%8C-cargotoml-%E9%87%8C%E7%9A%84%E7%89%88%E6%9C%AC%E4%BF%9D%E6%8C%81%E4%B8%80%E8%87%B4) + - [Tip 2: 不要在 dependencies 里添加 solana-sdk,因为这是 offchain 使用的](#tip-2-%E4%B8%8D%E8%A6%81%E5%9C%A8-dependencies-%E9%87%8C%E6%B7%BB%E5%8A%A0-solana-sdk%E5%9B%A0%E4%B8%BA%E8%BF%99%E6%98%AF-offchain-%E4%BD%BF%E7%94%A8%E7%9A%84) + - [Tip 3: 关于 buffer accounts](#tip-3-%E5%85%B3%E4%BA%8E-buffer-accounts) +- [重新部署](#%E9%87%8D%E6%96%B0%E9%83%A8%E7%BD%B2) +- [最佳实践](#%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5) + - [安装 solana-cli 的最佳实践](#%E5%AE%89%E8%A3%85-solana-cli-%E7%9A%84%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5) +- [如何查看部署的 program](#%E5%A6%82%E4%BD%95%E6%9F%A5%E7%9C%8B%E9%83%A8%E7%BD%B2%E7%9A%84-program) +- [客户端调用](#%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%B0%83%E7%94%A8) + - [客户调用程序 (Rust) (invoke solana program)](#%E5%AE%A2%E6%88%B7%E8%B0%83%E7%94%A8%E7%A8%8B%E5%BA%8F-rust-invoke-solana-program) + - [客户端调用(TypeScript)](#%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%B0%83%E7%94%A8typescript) +- [一些实验](#%E4%B8%80%E4%BA%9B%E5%AE%9E%E9%AA%8C) + - [哪些版本能成功编译和测试](#%E5%93%AA%E4%BA%9B%E7%89%88%E6%9C%AC%E8%83%BD%E6%88%90%E5%8A%9F%E7%BC%96%E8%AF%91%E5%92%8C%E6%B5%8B%E8%AF%95) +- [测试](#%E6%B5%8B%E8%AF%95) + - [测试(Rust)](#%E6%B5%8B%E8%AF%95rust) + - [测试(NodeJS)](#%E6%B5%8B%E8%AF%95nodejs) + - [All in one test setup script](#all-in-one-test-setup-script) +- [TODO](#todo) +- [部署程序](#%E9%83%A8%E7%BD%B2%E7%A8%8B%E5%BA%8F) +- [下一步](#%E4%B8%8B%E4%B8%80%E6%AD%A5) +- [Refs](#refs) + +# 初始化工程 + +## 使用 Cargo 初始化工程 + +我们可以使用 cargo 来初始化工程。 + +```bash +cargo init hello_world --lib +``` + +# 编写代码 + +## 程序入口 entrypoint + +下面利用 `entrypoint` 来编写程序入口。 + +`entrypoint` macro 需要一个函数参数,作为 solana program 的入口函数。 + +```rust +pub fn process_instruction() -> ProgramResult { + msg!("Hello, world!"); + Ok(()) +} +``` + +如果传递给 `entrypoint` macro 的函数签名不符合要求,编译时会报错: + +```bash + Checking hello_world v0.1.0 (/Users/dudu/Documents/projects/__projects__/solana/projects/hello_world) +error[E0061]: this function takes 0 arguments but 3 arguments were supplied + --> src/lib.rs:6:1 + | +6 | entrypoint!(process_instruction); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | unexpected argument #1 of type `&Pubkey` + | unexpected argument #2 of type `&Vec>` + | unexpected argument #3 of type `&[u8]` + | +note: function defined here + --> src/lib.rs:8:8 + | +8 | pub fn process_instruction() -> ProgramResult { + | ^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `entrypoint` (in Nightly builds, run with -Z macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0061`. +error: could not compile `hello_world` (lib) due to 1 previous error +``` + +## 修改 process_instruction 函数的签名 + +给 `process_instruction` 函数添加三个参数: + +- `program_id`: `&Pubkey` 类型,表示当前程序的公钥地址 +- `accounts`: `&[AccountInfo]` 类型,是一个 AccountInfo 数组的引用,包含了交易涉及的所有账户信息 +- `instruction_data`: `&[u8]` 类型,是指令的输入数据,以字节数组的形式传入 + +这三个参数是 Solana 程序执行时的基本要素: + +- `program_id` 用于验证程序身份和权限 +- `accounts` 包含了程序需要读取或修改的所有账户数据 +- `instruction_data` 携带了调用程序时传入的具体指令数据 + +```rust +pub fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + msg!("Hello, world!"); + Ok(()) +} +``` + +注意这里参数名前加了下划线前缀(`_`),是因为在这个简单的示例中我们暂时没有使用这些参数,这样可以避免编译器的未使用变量警告。在实际开发中,这些参数都是非常重要的,我们会在后续的示例中详细介绍如何使用它们。 + +关于函数签名,我们也可以[参考 solana_program_entrypoint 这个 crate 的文档](https://docs.rs/solana-program-entrypoint/latest/solana_program_entrypoint/macro.entrypoint.html): + +```rust +/// fn process_instruction( +/// program_id: &Pubkey, // Public key of the account the program was loaded into +/// accounts: &[AccountInfo], // All accounts required to process the instruction +/// instruction_data: &[u8], // Serialized instruction-specific data +/// ) -> ProgramResult; +``` + +# 构建程序 + +## 使用 cargo build-sbf 构建程序 + +为了构建 solana program,我们需要使用 `cargo build-sbf` 程序。 + +```bash +cargo build-sbf +``` + +构建失败了,以下是报错信息。 + +``` +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> cargo build-sbf +error: package `solana-program v2.1.4` cannot be built because it requires rustc 1.79.0 or newer, while the currently active rustc version is 1.75.0-dev +Either upgrade to rustc 1.79.0 or newer, or use +cargo update solana-program@2.1.4 --precise ver +where `ver` is the latest version of `solana-program` supporting rustc 1.75.0-dev +``` + +我们可以通过 `--version` 参数来查看 `rustc` 的版本信息。 + +```bash +cargo-build-sbf --version +``` + +输出: + +``` +solana-cargo-build-sbf 1.18.25 +platform-tools v1.41 +rustc 1.75.0 +``` + +关于系统版本的 rust compiler 和 build-sbf 使用的 rust compiler 不对应的问题,可以参考这个 issue。 +https://github.com/solana-labs/solana/issues/34987 + +## 解决 build-sbf 编译失败问题 + +一种方式是使用旧版本的 `solana-program`,如 `=1.17.0` 版本。 + +```toml +[package] +name = "hello_world" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +solana-program = "=1.17.0" +# solana-program = "=1.18.0" +``` + +但是运行 `cargo build-sbf` 之后,出现了另外的错误。 + +```bash +error: failed to parse lock file at: /Users/dudu/Documents/projects/__projects__/solana/projects/hello_world/Cargo.lock + +Caused by: + lock file version 4 requires `-Znext-lockfile-bump` +``` + +猜测可能是 `build-sbf` 使用的 cargo 版本不支持 version = 4 版本的 `Cargo.lock` 文件,而这个是编辑器(vscode/cursor)打开的状态下,rust-analyser 自动生成的。 + +安装 `stable` 版本的 solana cli 工具链: `sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"`,发现还是无法编译,报错如下: + +```bash +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" +downloading stable installer + ✨ stable commit 7104d71 initialized +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> cargo build-sbf --version +solana-cargo-build-sbf 2.0.17 +platform-tools v1.42 + +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> cargo build-sbf +[2024-12-04T11:14:48.052020000Z ERROR cargo_build_sbf] Failed to install platform-tools: HTTP status client error (404 Not Found) for url (https://github.com/anza-xyz/platform-tools/releases/download/v1.42/platform-tools-osx-x86_64.tar.bz2) +``` + +在进行 `cargo build-sbf` 编译的时候,需要下载对应版本的 `platform-tools`,因为未发布针对 Mac(Intel) 的 `v1.42` 版本 的 `platform-tools`,所以上述命令运行失败。 + +```bash +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> cargo build-sbf + Compiling cc v1.2.2 + Compiling serde v1.0.215 + Compiling solana-frozen-abi-macro v1.17.0 + Compiling ahash v0.7.8 + Compiling solana-frozen-abi v1.17.0 + Compiling either v1.13.0 + Compiling bs58 v0.4.0 + Compiling log v0.4.22 + Compiling hashbrown v0.11.2 + Compiling itertools v0.10.5 + Compiling solana-sdk-macro v1.17.0 + Compiling bytemuck v1.20.0 + Compiling borsh v0.9.3 + Compiling num-derive v0.3.3 + Compiling blake3 v1.5.5 + Compiling solana-program v1.17.0 + Compiling bv v0.11.1 + Compiling serde_json v1.0.133 + Compiling serde_bytes v0.11.15 + Compiling bincode v1.3.3 +Error: Function _ZN112_$LT$solana_program..instruction..InstructionError$u20$as$u20$solana_frozen_abi..abi_example..AbiEnumVisitor$GT$13visit_for_abi17hc69c00f4c61717f8E Stack offset of 6640 exceeded max offset of 4096 by 2544 bytes, please minimize large stack variables. Estimated function frame size: 6680 bytes. Exceeding the maximum stack offset may cause undefined behavior during execution. + + Compiling hello_world v0.1.0 (/Users/dudu/Documents/projects/__projects__/solana/projects/hello_world) + Finished `release` profile [optimized] target(s) in 25.19s ++ ./platform-tools/rust/bin/rustc --version ++ ./platform-tools/rust/bin/rustc --print sysroot ++ set +e ++ rustup toolchain uninstall solana +info: uninstalling toolchain 'solana' +info: toolchain 'solana' uninstalled ++ set -e ++ rustup toolchain link solana platform-tools/rust ++ exit 0 +⏎ + +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> ls target/deploy/ +hello_world-keypair.json hello_world.so +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> cargo build-sbf --version +solana-cargo-build-sbf 2.1.4 +platform-tools v1.43 +rustc 1.79.0 + +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master) [1]> sh -c "$(curl -sSfL https://release.anza.xyz/beta/install)" +downloading beta installer + ✨ beta commit 024d047 initialized +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> cargo build-sbf --version +solana-cargo-build-sbf 2.1.4 +platform-tools v1.43 +rustc 1.79.0 +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> cargo build-sbf +Error: Function _ZN112_$LT$solana_program..instruction..InstructionError$u20$as$u20$solana_frozen_abi..abi_example..AbiEnumVisitor$GT$13visit_for_abi17hc69c00f4c61717f8E Stack offset of 6640 exceeded max offset of 4096 by 2544 bytes, please minimize large stack variables. Estimated function frame size: 6680 bytes. Exceeding the maximum stack offset may cause undefined behavior during execution. + + Finished `release` profile [optimized] target(s) in 0.23s +``` + +使用 `beta` 版本的 solana cli tool suites 虽然能够编译,但是遇到了这个错误: + +`Exceeding the maximum stack offset may cause undefined behavior during execution.` + +``` + Compiling bincode v1.3.3 +Error: Function _ZN112_$LT$solana_program..instruction..InstructionError$u20$as$u20$solana_frozen_abi..abi_example..AbiEnumVisitor$GT$13visit_for_abi17hc69c00f4c61717f8E Stack offset of 6640 exceeded max offset of 4096 by 2544 bytes, please minimize large stack variables. Estimated function frame size: 6680 bytes. Exceeding the maximum stack offset may cause undefined behavior during execution. +``` + +具体原因依旧是老生常谈的版本问题,原因分析可以参考: +https://solana.stackexchange.com/questions/16443/error-function-stack-offset-of-7256-exceeded-max-offset-of-4096-by-3160-bytes + +尝试更新 `solana-program` 的版本到 `2.1.4` 之后(运行 `sh -c "$(curl -sSfL https://release.anza.xyz/v2.1.4/install)"`),用以下版本的工具链进行编译: + +```bash +> cargo build-sbf --version +solana-cargo-build-sbf 2.1.4 +platform-tools v1.43 +rustc 1.79.0 + +# solana-cargo-build-sbf 2.2.0 +# platform-tools v1.43 +# rustc 1.79.0 +``` + +运行 `cargo build-sbf`: + +```bash +> cargo build-sbf + Compiling serde v1.0.215 + Compiling equivalent v1.0.1 + Compiling hashbrown v0.15.2 + Compiling toml_datetime v0.6.8 + Compiling syn v2.0.90 + Compiling winnow v0.6.20 + Compiling cfg_aliases v0.2.1 + Compiling once_cell v1.20.2 + Compiling borsh v1.5.3 + Compiling solana-define-syscall v2.1.4 + Compiling solana-sanitize v2.1.4 + Compiling solana-atomic-u64 v2.1.4 + Compiling bs58 v0.5.1 + Compiling bytemuck v1.20.0 + Compiling five8_core v0.1.1 + Compiling five8_const v0.1.3 + Compiling solana-decode-error v2.1.4 + Compiling solana-msg v2.1.4 + Compiling cc v1.2.2 + Compiling solana-program-memory v2.1.4 + Compiling log v0.4.22 + Compiling solana-native-token v2.1.4 + Compiling solana-program-option v2.1.4 + Compiling indexmap v2.7.0 + Compiling blake3 v1.5.5 + Compiling toml_edit v0.22.22 + Compiling serde_derive v1.0.215 + Compiling bytemuck_derive v1.8.0 + Compiling solana-sdk-macro v2.1.4 + Compiling thiserror-impl v1.0.69 + Compiling num-derive v0.4.2 + Compiling proc-macro-crate v3.2.0 + Compiling borsh-derive v1.5.3 + Compiling thiserror v1.0.69 + Compiling solana-secp256k1-recover v2.1.4 + Compiling solana-borsh v2.1.4 + Compiling solana-hash v2.1.4 + Compiling bincode v1.3.3 + Compiling bv v0.11.1 + Compiling solana-serde-varint v2.1.4 + Compiling serde_bytes v0.11.15 + Compiling solana-fee-calculator v2.1.4 + Compiling solana-short-vec v2.1.4 + Compiling solana-sha256-hasher v2.1.4 + Compiling solana-pubkey v2.1.4 + Compiling solana-instruction v2.1.4 + Compiling solana-sysvar-id v2.1.4 + Compiling solana-slot-hashes v2.1.4 + Compiling solana-clock v2.1.4 + Compiling solana-epoch-schedule v2.1.4 + Compiling solana-last-restart-slot v2.1.4 + Compiling solana-rent v2.1.4 + Compiling solana-program-error v2.1.4 + Compiling solana-stable-layout v2.1.4 + Compiling solana-serialize-utils v2.1.4 + Compiling solana-account-info v2.1.4 + Compiling solana-program-pack v2.1.4 + Compiling solana-bincode v2.1.4 + Compiling solana-slot-history v2.1.4 + Compiling solana-program-entrypoint v2.1.4 + Compiling solana-cpi v2.1.4 + Compiling solana-program v2.1.4 + Compiling hello_world v0.1.0 (/Users/dudu/Documents/projects/__projects__/solana/projects/hello_world) + Finished `release` profile [optimized] target(s) in 50.87s +``` + +总算编译成功了,开瓶香槟庆祝一下吧! + +这里是 Cargo.toml 文件: + +```toml +[package] +name = "hello_world" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +solana-program = "2.1.4" +# solana-program = "=1.17.0" +``` + +# 部署 + +当我们通过运行 `solana program deploy` 命令来部署程序的时候,部署失败了。 + +```bash +dudu@tale ~/Documents/projects/__projects__/solana/projects/helloworld (master)> solana program deploy ./target/deploy/helloworld.so +⠁ 0.0% | Sending 1/173 transactions [block height 2957; re-sign in 150 blocks] + thread 'main' panicked at quic-client/src/nonblocking/quic_client.rs:142:14: +QuicLazyInitializedEndpoint::create_endpoint bind_in_range: Os { code: 55, kind: Uncategorized, message: "No buffer space available" } +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +``` + +那么这个 `No buffer space available` 是什么意思呢? + +排查了很久终于无果,凭借多年的经验,大概率应该是 **版本** 的问题,因为通过 `Anchor` 创建的工程是能够正常部署的。 + +这里记录一下 `solana` 命令的版本信息: + +```bash +> solana --version +solana-cli 2.2.0 (src:67704836; feat:1081947060, client:Agave) +``` + +## 回到 Anchor 工程验证部署失败源自版本的问题 + +我们可以通过 `anchor init helloworld` 新建工程,并通过 `anchor build` 和 `anchor deploy` 来部署程序。 + +```bash +anchor init helloworld +cd helloworld +anchor build +anchor deploy +``` + +从出错信息了解到,全新生成的 anchor 工程部署的时候会发生同样的错误:`No buffer space available` + +```bash +dudu@tale ~/tmp/helloworld (main)> anchor deploy +Deploying cluster: https://api.devnet.solana.com +Upgrade authority: /Users/dudu/.config/solana/id.json +Deploying program "helloworld"... +Program path: /Users/dudu/tmp/helloworld/target/deploy/helloworld.so... +⠁ 0.0% | Sending 1/180 transactions [block height 332937196; re-sign in 150 blocks] thread 'main' panicked at quic-client/src/nonblocking/quic_client.rs:142:14: +QuicLazyInitializedEndpoint::create_endpoint bind_in_range: Os { code: 55, kind: Uncategorized, message: "No buffer space available" } +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +There was a problem deploying: Output { status: ExitStatus(unix_wait_status(25856)), stdout: "", stderr: "" }. +``` + +检查下 anchor 的版本: + +```bash +dudu@tale ~/tmp/helloworld (main)> anchor deploy --help +Deploys each program in the workspace + +Usage: anchor-0.30.1 deploy [OPTIONS] [-- ...] + +Arguments: + [SOLANA_ARGS]... Arguments to pass to the underlying `solana program deploy` command + +Options: + -p, --program-name Only deploy this program + --provider.cluster Cluster override + --program-keypair Keypair of the program (filepath) (requires program-name) + --provider.wallet Wallet override + -v, --verifiable If true, deploy from path target/verifiable + -h, --help Print help +``` + +检查下 solana 的版本: + +```bash +> solana --version +solana-cli 2.2.0 (src:67704836; feat:1081947060, client:Agave) +``` + +这个 `2.2.0` 的版本看着有些奇怪,忽然想到为了编译 solana 程序,我安装了 edge 版本的 solana cli,其携带的 solana cli 的版本是 `2.2.0`: + +```bash +sh -c "$(curl -sSfL https://release.anza.xyz/edge/install)" +``` + +于是换回了 `stable` 版本: + +```bash +> sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" +downloading stable installer + ✨ stable commit fbead11 initialized +``` + +而 stable 版本的 solana 是 `2.0.19`。 + +```bash +> solana --version +solana-cli 2.0.19 (src:fbead118; feat:607245837, client:Agave) +``` + +重新部署程序之前,我们先来清理下之前部署失败的程序的 `buffers`,也就是 buffer accounts。关于什么是 buffer accounts,请参考 Tips 3。 + +- 查看所有的 buffer accounts: `solana program show --buffers` +- 关闭所有的 buffer accounts: `solana program close --buffers` + - 关闭 buffer accounts 可以回收存储在 buffer accounts 里的 SOL + +```bash +Error: error sending request for url (https://api.devnet.solana.com/): operation timed out +dudu@tale ~/tmp/helloworld (main)> solana program show --buffers + +Buffer Address | Authority | Balance +CcKFVBzcsrcReZHBLnwzkQbNGXoK4hUee7hkgtbHCKtL | FCxBXdduz9HqTEPvEBSuFLLAjbVYh9a5ZgEnZwKyN2ZH | 0.12492504 SOL +62wFzMYBhxWg4ntEJmFZcQ3P3Qtm9SbaBcbTmV8o8yPk | FCxBXdduz9HqTEPvEBSuFLLAjbVYh9a5ZgEnZwKyN2ZH | 0.12492504 SOL +9q88jzvR5AdPdNTihxWroxRL7cBWQ5xXepNfDdaqmMTv | FCxBXdduz9HqTEPvEBSuFLLAjbVYh9a5ZgEnZwKyN2ZH | 1.26224472 SOL +3nqzHv9vUphsmAjoR1C5ShgZ54muTzkZZ6Z4NKfqrKqt | FCxBXdduz9HqTEPvEBSuFLLAjbVYh9a5ZgEnZwKyN2ZH | 1.26224472 SOL +8tZ8YYA1WS6WFVyEbJAdgnszXYZwwq7b9RLdoiry2Fb1 | FCxBXdduz9HqTEPvEBSuFLLAjbVYh9a5ZgEnZwKyN2ZH | 0.12492504 SOL + +dudu@tale ~/tmp/helloworld (main)> solana program close --buffers + +Buffer Address | Authority | Balance +CcKFVBzcsrcReZHBLnwzkQbNGXoK4hUee7hkgtbHCKtL | FCxBXdduz9HqTEPvEBSuFLLAjbVYh9a5ZgEnZwKyN2ZH | 0.12492504 SOL +62wFzMYBhxWg4ntEJmFZcQ3P3Qtm9SbaBcbTmV8o8yPk | FCxBXdduz9HqTEPvEBSuFLLAjbVYh9a5ZgEnZwKyN2ZH | 0.12492504 SOL +9q88jzvR5AdPdNTihxWroxRL7cBWQ5xXepNfDdaqmMTv | FCxBXdduz9HqTEPvEBSuFLLAjbVYh9a5ZgEnZwKyN2ZH | 1.26224472 SOL +3nqzHv9vUphsmAjoR1C5ShgZ54muTzkZZ6Z4NKfqrKqt | FCxBXdduz9HqTEPvEBSuFLLAjbVYh9a5ZgEnZwKyN2ZH | 1.26224472 SOL +8tZ8YYA1WS6WFVyEbJAdgnszXYZwwq7b9RLdoiry2Fb1 | FCxBXdduz9HqTEPvEBSuFLLAjbVYh9a5ZgEnZwKyN2ZH | 0.12492504 SOL +``` + +好了 buffer accounts 清理完毕,此时我们也换回了 `stable` 版本的 solana cli,我们再尝试部署程序: + +```bash +> anchor deploy +Deploying cluster: https://api.devnet.solana.com +Upgrade authority: /Users/dudu/.config/solana/id.json +Deploying program "helloworld"... +Program path: /Users/dudu/tmp/helloworld/target/deploy/helloworld.so... +Program Id: DiSGTiXGq4HXCxq1pAibuGZjSpKT4Av8WShvuuYhTks9 + +Signature: 2EXHmU68k9SmJ5mXuM61pFDnUgozbJZ5ihHChPqFMVgjRJy4zCqnq6NAbvDkfiHd29xsmW4Vr3Kk6wHFbLEdCEZb + +Deploy success +``` + +成功了 🎉,再开一瓶香槟庆祝下吧! + +这更加深了我们的猜测:版本问题导致程序无法部署。 + +## 再回来部署我们的 hello_world 工程 + +好了,验证了部署失败不是工程类型(anchor project or cargo projct)导致的原因之后,我们再回到 `cargo init` 创建的工程:`hello_world`. + +我们可以通过 `solana` 的子命令来部署程序: 运行 `solana program deploy ./target/deploy/helloworld.so` 部署程序。 + +我们会分别在 `localnet` 和 `devnet` 部署。 + +### localnet 部署 + +首先是 `localnet` 部署。 + +切换环境到 localnet: + +```bash +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> solana_local +Config File: /Users/dudu/.config/solana/cli/config.yml +RPC URL: http://localhost:8899 +WebSocket URL: ws://localhost:8900/ (computed) +Keypair Path: /Users/dudu/.config/solana/id.json +Commitment: confirmed +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> solana config get +Config File: /Users/dudu/.config/solana/cli/config.yml +RPC URL: http://localhost:8899 +WebSocket URL: ws://localhost:8900/ (computed) +Keypair Path: /Users/dudu/.config/solana/id.json +Commitment: confirmed +``` + +部署程序: + +```bash +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> solana program deploy ./target/deploy/hello_world.so +Program Id: DhQr1KGGQcf8BeU5uQvR35p2kgKqEinD45PRTDDRqx7z + +Signature: 3WVEWN4NUodsb8ZDjbjrTWXLikZ7wbWCuzuRZtSBmyKL4kVvESSeLwKZ3cJo1At4vDcaBs5iEcHhdteyXCwqwmDw +``` + +### devnet 部署 + +下面是 `devnet` 部署。 + +切换环境到 localnet: + +```bash +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> solana_devnet +Config File: /Users/dudu/.config/solana/cli/config.yml +RPC URL: https://api.devnet.solana.com +WebSocket URL: wss://api.devnet.solana.com/ (computed) +Keypair Path: /Users/dudu/.config/solana/id.json +Commitment: confirmed + +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> solana config get +Config File: /Users/dudu/.config/solana/cli/config.yml +RPC URL: https://api.devnet.solana.com +WebSocket URL: wss://api.devnet.solana.com/ (computed) +Keypair Path: /Users/dudu/.config/solana/id.json +Commitment: confirmed + +dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> solana program deploy ./target/deploy/hello_world.so +Program Id: DhQr1KGGQcf8BeU5uQvR35p2kgKqEinD45PRTDDRqx7z + +Signature: 4P89gHNUNccQKJAsE3aXJVpFrWeqLxcmk9SYHbQCX7T1sEvyPrxcbrAeJbk8F8YKwWT79nTswSZkz7mtSb55nboF +``` + +我们可以通过 solana balance 来查询下部署前后的余额 + +```bash +# 部署之前余额 +(base) dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> solana balance +75.153619879 SOL + +# 部署之后余额 +(base) dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> solana balance +75.152378439 SOL +``` + +而此时的版本: + +```bash +dudu@tale ~/Documents/projects/__projects__/solana/projects/helloworld (master)> solana --version +solana-cli 2.0.19 (src:fbead118; feat:607245837, client:Agave) +``` + +由此可见,不要尝鲜用最新的版本(solana-cli `2.2.0`),否则会弄巧成拙。 + +# Tips + +## Tip 1: solana cli 的版本和 Cargo.toml 里的版本保持一致 + +在 [solana 的官方教程](https://solana.com/developers/guides/getstarted/local-rust-hello-world#create-a-new-rust-library-with-cargo)里提到这个 Tip: + +> It is highly recommended to keep your solana-program and other Solana Rust dependencies in-line with your installed version of the Solana CLI. For example, if you are running Solana CLI 2.0.3, you can instead run: + +```bash +cargo add solana-program@"=2.0.3" +``` + +> This will ensure your crate uses only 2.0.3 and nothing else. If you experience compatibility issues with Solana dependencies, check out the + +## Tip 2: 不要在 dependencies 里添加 solana-sdk,因为这是 offchain 使用的 + +参考这里的说明: +https://solana.stackexchange.com/questions/9109/cargo-build-bpf-failed + +> I have identified the issue. The solana-sdk is designed for off-chain use only, so it should be removed from the dependencies. + +错误将 `solana-sdk` 添加到 dependencies 报错: + +```bash + Compiling autocfg v1.4.0 + Compiling jobserver v0.1.32 +error: target is not supported, for more information see: https://docs.rs/getrandom/#unsupported-targets + --> src/lib.rs:267:9 + | +267 | / compile_error!("\ +268 | | target is not supported, for more information see: \ +269 | | https://docs.rs/getrandom/#unsupported-targets\ +270 | | "); + | |__________^ + +error[E0433]: failed to resolve: use of undeclared crate or module `imp` + --> src/lib.rs:291:5 + | +291 | imp::getrandom_inner(dest) + | ^^^ use of undeclared crate or module `imp` + +For more information about this error, try `rustc --explain E0433`. +error: could not compile `getrandom` (lib) due to 2 previous errors +warning: build failed, waiting for other jobs to finish... +``` + +## Tip 3: 关于 buffer accounts + +在 Solana 中,buffer accounts 是用于程序部署过程中的一种临时账户,它是 Solana 部署程序时的一个重要机制。由于 Solana 的交易大小限制为 `1232` 字节,部署程序时通常需要多个交易步骤。在这个过程中,buffer account 的作用是存储程序的字节码,直到部署完成。 + +buffer account 的关键点: + +- 临时存储:buffer account 用于存放程序的字节码,确保在部署过程中能够处理较大的程序。 +- 自动关闭:一旦程序成功部署,相关的 buffer account 会自动关闭,从而释放占用的资源。 +- 失败处理:如果部署失败,buffer account 不会自动删除,用户可以选择: + - 继续使用现有的 buffer account 来完成部署。 + - 关闭 buffer account,以便回收已分配的 SOL(租金)。 +- 检查 buffer accounts:可以通过命令 `solana program show --buffers` 来检查当前是否存在未关闭的 buffer accounts。 +- 关闭 buffer accounts:可以通过命令 `solana program close --buffers` 来关闭 buffer accounts。 + +关于 solana 程序部署的过程的解释,可以查考官方文档: https://solana.com/docs/programs/deploying#program-deployment-process + +# 重新部署 + +重新部署只需要编辑代码之后运行 `cargo build-sbf` 编译代码,再通过 `solana program deply ./target/deploy/hello_world.so` 部署即可。 + +```bash +cargo build-sbf +solana program deploy ./target/deploy/hello_world.so +``` + +可以通过运行测试和 client 脚本来验证运行的是新版本的 program。 + +```bash +# 运行测试 +cargo test-sbf +# 运行 client 脚本 +cargo run --example client +``` + +比如,我修改 `msg!` 输入内容为 `Hello, world! GM!GN!`,运行测试和 client 脚本能够看到 log 里有这个输出。 + +```rust +pub fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + msg!("Hello, world! GM!GN!"); + Ok(()) +} +``` + +运行测试: + +``` +(base) dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> cargo test-sbf + Compiling hello_world v0.1.0 (/Users/dudu/Documents/projects/__projects__/solana/projects/hello_world) + Finished release [optimized] target(s) in 1.76s + Compiling hello_world v0.1.0 (/Users/dudu/Documents/projects/__projects__/solana/projects/hello_world) + Finished `test` profile [unoptimized + debuginfo] target(s) in 13.92s + Running unittests src/lib.rs (target/debug/deps/hello_world-ee1a919556768e26) + +running 1 test +[2024-12-06T08:06:57.714248000Z INFO solana_program_test] "hello_world" SBF program from /Users/dudu/Documents/projects/__projects__/solana/projects/hello_world/target/deploy/hello_world.so, modified 19 seconds, 228 ms, 255 µs and 392 ns ago +[2024-12-06T08:06:57.947344000Z DEBUG solana_runtime::message_processor::stable_log] Program 1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM invoke [1] +[2024-12-06T08:06:57.947695000Z DEBUG solana_runtime::message_processor::stable_log] Program log: Hello, world! GM!GN! +[2024-12-06T08:06:57.947738000Z DEBUG solana_runtime::message_processor::stable_log] Program 1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM consumed 140 of 200000 compute units +[2024-12-06T08:06:57.947897000Z DEBUG solana_runtime::message_processor::stable_log] Program 1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM success +test test::test_hello_world ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.24s + + Doc-tests hello_world + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +``` + +TODO: image + +# 最佳实践 + +## 安装 solana-cli 的最佳实践 + +最好的方式是安装指定版本的 solana cli,如可以用以下方式安装 `2.0.3` 的版本: + +```bash +# 安装 stable 和 beta 都不推荐 +# sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" +# sh -c "$(curl -sSfL https://release.anza.xyz/beta/install)" +# 推荐安装指定版本 +sh -c "$(curl -sSfL https://release.anza.xyz/v2.0.3/install)" +``` + +输出: + +``` +downloading v2.0.3 installer + ✨ 2.0.3 initialized +``` + +运行 `cargo build-sbf --version` 查看下 `cargo build-sbf` 的版本: + +```bash +(base) dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master) [1]> cargo build-sbf --version +solana-cargo-build-sbf 2.0.3 +platform-tools v1.41 +rustc 1.75.0 +``` + +可以看到,这里的 rustc 版本是 `1.75.0`,比较老旧,编译的时候必须带上 `-Znext-lockfile-bump` 参数,否则编译出错: + +```bash +(base) dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> cargo build-sbf +info: uninstalling toolchain 'solana' +info: toolchain 'solana' uninstalled +error: failed to parse lock file at: /Users/dudu/Documents/projects/__projects__/solana/projects/hello_world/Cargo.lock + +Caused by: + lock file version 4 requires `-Znext-lockfile-bump` +``` + +以下是传递 `-Znext-lockfile-bump` 参数之后,完整的编译过程: + +```bash +(base) dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> cargo build-sbf -- -Znext-lockfile-bump + Compiling proc-macro2 v1.0.92 + Compiling unicode-ident v1.0.14 + Compiling version_check v0.9.5 + Compiling typenum v1.17.0 + Compiling autocfg v1.4.0 + Compiling serde v1.0.215 + Compiling syn v1.0.109 + Compiling cfg-if v1.0.0 + Compiling equivalent v1.0.1 + Compiling hashbrown v0.15.2 + Compiling semver v1.0.23 + Compiling generic-array v0.14.7 + Compiling ahash v0.8.11 + Compiling winnow v0.6.20 + Compiling indexmap v2.7.0 + Compiling toml_datetime v0.6.8 + Compiling shlex v1.3.0 + Compiling quote v1.0.37 + Compiling subtle v2.6.1 + Compiling cc v1.2.2 + Compiling syn v2.0.90 + Compiling once_cell v1.20.2 + Compiling rustversion v1.0.18 + Compiling feature-probe v0.1.1 + Compiling zerocopy v0.7.35 + Compiling cfg_aliases v0.2.1 + Compiling borsh v1.5.3 + Compiling bv v0.11.1 + Compiling rustc_version v0.4.1 + Compiling num-traits v0.2.19 + Compiling memoffset v0.9.1 + Compiling thiserror v1.0.69 + Compiling toml_edit v0.22.22 + Compiling blake3 v1.5.5 + Compiling block-buffer v0.10.4 + Compiling crypto-common v0.1.6 + Compiling solana-program v2.0.3 + Compiling digest v0.10.7 + Compiling hashbrown v0.13.2 + Compiling constant_time_eq v0.3.1 + Compiling bs58 v0.5.1 + Compiling arrayvec v0.7.6 + Compiling arrayref v0.3.9 + Compiling keccak v0.1.5 + Compiling sha2 v0.10.8 + Compiling toml v0.5.11 + Compiling sha3 v0.10.8 + Compiling proc-macro-crate v3.2.0 + Compiling borsh-derive-internal v0.10.4 + Compiling borsh-schema-derive-internal v0.10.4 + Compiling getrandom v0.2.15 + Compiling lazy_static v1.5.0 + Compiling bytemuck v1.20.0 + Compiling log v0.4.22 + Compiling proc-macro-crate v0.1.5 + Compiling serde_derive v1.0.215 + Compiling thiserror-impl v1.0.69 + Compiling num-derive v0.4.2 + Compiling solana-sdk-macro v2.0.3 + Compiling bytemuck_derive v1.8.0 + Compiling borsh-derive v1.5.3 + Compiling borsh-derive v0.10.4 + Compiling borsh v0.10.4 + Compiling serde_bytes v0.11.15 + Compiling bincode v1.3.3 + Compiling hello_world v0.1.0 (/Users/dudu/Documents/projects/__projects__/solana/projects/hello_world) + Finished release [optimized] target(s) in 2m 28s ++ ./platform-tools/rust/bin/rustc --version ++ ./platform-tools/rust/bin/rustc --print sysroot ++ set +e ++ rustup toolchain uninstall solana +info: uninstalling toolchain 'solana' +info: toolchain 'solana' uninstalled ++ set -e ++ rustup toolchain link solana platform-tools/rust ++ exit 0 +``` + +值得注意的是,无论是安装 stable 版本还是 beta 版本都会导致编译失败,stable 版本运行 `cargo build-sbf` 会去 github release 页面下载针对 `x86_64` 架构的 platform-tools,但是官方没有发布提供针对这个版本的 platform-tools。以下是出错信息: + +```bash +(base) dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master) [1]> cargo build-sbf --version +solana-cargo-build-sbf 2.0.19 +platform-tools v1.42 + +(base) dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master) [1]> cargo build-sbf +[2024-12-05T06:17:30.547088000Z ERROR cargo_build_sbf] Failed to install platform-tools: HTTP status client error (404 Not Found) for url (https://github.com/anza-xyz/platform-tools/releases/download/v1.42/platform-tools-osx-x86_64.tar.bz2) +``` + +发现如果指定 `--tools-version` 为 `v1.43` 也不能成功编译。 + +```bash +(base) dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master) [1]> cargo build-sbf --tools-version v1.43 + Blocking waiting for file lock on package cache + Blocking waiting for file lock on package cache + Compiling blake3 v1.5.5 + Compiling solana-program v2.0.3 + Compiling bs58 v0.5.1 + Compiling solana-sdk-macro v2.0.3 + Compiling hello_world v0.1.0 (/Users/dudu/Documents/projects/__projects__/solana/projects/hello_world) + Finished `release` profile [optimized] target(s) in 1m 16s ++ curl -L https://github.com/anza-xyz/platform-tools/releases/download/v1.42/platform-tools-osx-x86_64.tar.bz2 -o platform-tools-osx-x86_64.tar.bz2 + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed +100 9 100 9 0 0 16 0 --:--:-- --:--:-- --:--:-- 16 ++ tar --strip-components 1 -jxf platform-tools-osx-x86_64.tar.bz2 +tar: Error opening archive: Unrecognized archive format ++ return 1 ++ popd ++ return 1 +/Users/dudu/.local/share/solana/install/releases/stable-fbead118867c08e6c3baaf8d196897c2536f067a/solana-release/bin/sdk/sbf/scripts/strip.sh: line 23: /Users/dudu/.local/share/solana/install/releases/stable-fbead118867c08e6c3baaf8d196897c2536f067a/solana-release/bin/sdk/sbf/dependencies/platform-tools/llvm/bin/llvm-objcopy: No such file or directory +``` + +所以还是老老实实安装指定版本的 solana cli 吧。 + +# 如何查看部署的 program + +我们可以通过访问以下地址来查看部署的 program。 + +https://explorer.solana.com/?cluster=custom + +它会自动用本地的 localhost:8899 作为 rpc endpoint,在搜索栏搜索 program id,即可看到 transaction 详情。 + +TODO: image + +# 客户端调用 + +## 客户调用程序 (Rust) (invoke solana program) + +首先创建 `examples` 目录,并在 `examples` 目录下创建 `client.rs` 文件。 + +```bash +mkdir -p examples +touch examples/client.rs +``` + +在 `Cargo.toml` 增加以下内容: + +```toml +[[example]] +name = "client" +path = "examples/client.rs" +``` + +添加 `solana-client` 依赖: + +```bash +cargo add solana-client@1.18.26 --dev +``` + +添加以下代码到 `examples/client.rs`,注意替换你自己部署的 program ID: + +```rust +use solana_client::rpc_client::RpcClient; +use solana_sdk::{ + commitment_config::CommitmentConfig, + instruction::Instruction, + pubkey::Pubkey, + signature::{Keypair, Signer}, + transaction::Transaction, +}; +use std::str::FromStr; + +#[tokio::main] +async fn main() { + // Program ID (replace with your actual program ID) + let program_id = Pubkey::from_str("85K3baeo8tvZBmuty2UP8mMVd1vZtxLkmeUkj1s6tnT6").unwrap(); + + // Connect to the Solana devnet + let rpc_url = String::from("http://127.0.0.1:8899"); + let client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed()); + + // Generate a new keypair for the payer + let payer = Keypair::new(); + + // Request airdrop + let airdrop_amount = 1_000_000_000; // 1 SOL + let signature = client + .request_airdrop(&payer.pubkey(), airdrop_amount) + .expect("Failed to request airdrop"); + + // Wait for airdrop confirmation + loop { + let confirmed = client.confirm_transaction(&signature).unwrap(); + if confirmed { + break; + } + } + + // Create the instruction + let instruction = Instruction::new_with_borsh( + program_id, + &(), // Empty instruction data + vec![], // No accounts needed + ); + + // Add the instruction to new transaction + let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey())); + transaction.sign(&[&payer], client.get_latest_blockhash().unwrap()); + + // Send and confirm the transaction + match client.send_and_confirm_transaction(&transaction) { + Ok(signature) => println!("Transaction Signature: {}", signature), + Err(err) => eprintln!("Error sending transaction: {}", err), + } +} +``` + +这个简单的脚本能够调用已部署的 solana program,它主要做了以下几件事: + +- 连接本地 RPC +- 创建新账户 +- 空投 1 SOL 给新开的账户 +- 创建 hello_world program 所需的指令(Instruction) +- 发送交易 (通过 `send_and_confirm_transaction`) + +关于 program ID,我们可以通过 `solana address -k .json` 命令来获取 program ID: + +```bash +solana address -k ./target/deploy/hello_world-keypair.json +``` + +`-k` 参数接收 keypair 的文件,可以获得 PublicKey。 + +运行 client: + +```bash +cargo run --example client +``` + +运行 client 代码的输出: + +```bash +(base) dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> cargo run --example client + Blocking waiting for file lock on package cache + Blocking waiting for file lock on package cache + Blocking waiting for file lock on package cache + Compiling hello_world v0.1.0 (/Users/dudu/Documents/projects/__projects__/solana/projects/hello_world) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 5.13s + Running `target/debug/examples/client` +Transaction Signature: iPcYzbBCM6kkXvdx5GQLS9WYunT6yWFAp8NeRyNH5ZHbjXNpGuT1pqLAmQZSa2g7mubuFmaCTxqPVS54J4Zz22h +``` + +## 客户端调用(TypeScript) + +我们可以通过建立 nodejs 工程来发送交易: + +```bash +mkdir -p helloworld +npm init -y +npm install --save-dev typescript +npm install @solana/web3.js@1 @solana-developers/helpers@2 +``` + +建立 `tsconfig.json` 配置文件: + +```json +{ + "compilerOptions": { + "target": "es2016", + "module": "commonjs", + "types": ["node"], + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +} +``` + +创建 `hello-world-client.ts` 文件,注意修改 `PublicKey` 的参数为你部署时生成的 programID: + +```typescript +import { + Connection, + PublicKey, + Transaction, + TransactionInstruction, +} from "@solana/web3.js"; +import { getKeypairFromFile } from "@solana-developers/helpers"; + +async function main() { + const programId = new PublicKey( + "DhQr1KGGQcf8BeU5uQvR35p2kgKqEinD45PRTDDRqx7z" + ); + + // Connect to a solana cluster. Either to your local test validator or to devnet + const connection = new Connection("http://localhost:8899", "confirmed"); + //const connection = new Connection("https://api.devnet.solana.com", "confirmed"); + + // We load the keypair that we created in a previous step + const keyPair = await getKeypairFromFile("~/.config/solana/id.json"); + + // Every transaction requires a blockhash + const blockhashInfo = await connection.getLatestBlockhash(); + + // Create a new transaction + const tx = new Transaction({ + ...blockhashInfo, + }); + + // Add our Hello World instruction + tx.add( + new TransactionInstruction({ + programId: programId, + keys: [], + data: Buffer.from([]), + }) + ); + + // Sign the transaction with your previously created keypair + tx.sign(keyPair); + + // Send the transaction to the Solana network + const txHash = await connection.sendRawTransaction(tx.serialize()); + + console.log("Transaction sent with hash:", txHash); + + await connection.confirmTransaction({ + blockhash: blockhashInfo.blockhash, + lastValidBlockHeight: blockhashInfo.lastValidBlockHeight, + signature: txHash, + }); + + console.log( + `Congratulations! Look at your ‘Hello World' transaction in the Solana Explorer: + https://explorer.solana.com/tx/${txHash}?cluster=custom` + ); +} + +main(); +``` + +运行: + +```bash +npx ts-node hello-world-client.ts +``` + +输出: + +```bash +(base) dudu@tale ~/Documents/projects/__projects__/solana/projects/solana-web3-example (master)> npx ts-node hello-world-client.ts +(node:4408) ExperimentalWarning: CommonJS module /usr/local/lib/node_modules/npm/node_modules/debug/src/node.js is loading ES Module /usr/local/lib/node_modules/npm/node_modules/supports-color/index.js using require(). +Support for loading ES Module in require() is an experimental feature and might change at any time +(Use `node --trace-warnings ...` to show where the warning was created) +(node:4467) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead. +(Use `node --trace-deprecation ...` to show where the warning was created) +Transaction sent with hash: 29aFYDNv1cyrByA8FTBxrhohJx3H1FVLSUordaA1RVcXSNSy7zN5mGW5rwj6pDuopMvvoBaKNHeKmQ8c17uVnqoN +Congratulations! Look at your ‘Hello World' transaction in the Solana Explorer: + https://explorer.solana.com/tx/29aFYDNv1cyrByA8FTBxrhohJx3H1FVLSUordaA1RVcXSNSy7zN5mGW5rwj6pDuopMvvoBaKNHeKmQ8c17uVnqoN?cluster=custom +``` + +TODO: image + +# 一些实验 + +## 哪些版本能成功编译和测试 + +首先看一下我们安装的 `build-sbf` 和 `test-sbf` 的版本: + +```bash +# build-sbf 版本 +> cargo build-sbf --version +solana-cargo-build-sbf 2.1.4 +platform-tools v1.43 +rustc 1.79.0 + +# test-sbf 版本 +> cargo test-sbf --version +solana-cargo-test-sbf 2.1.4 +``` + +我们通过这个命令来测试哪些版本能够正确编译和测试: `rm -rf target Cargo.lock && cargo build-sbf && cargo test-sbf` + +| version | DevDependencies & Dependencies | NOTE | +| --------- | ---------------------------------------------------------------------------------------------------------- | -------------- | +| ✅2.1.4 | `cargo add solana-sdk@2.1.4 solana-program-test@2.1.4 tokio --dev && cargo add solana-program@2.1.4` | latest version | +| ✅2.0.18 | `cargo add solana-sdk@2.0.18 solana-program-test@2.0.18 tokio --dev && cargo add solana-program@2.0.18` | latest version | +| ✅2.0.3 | `cargo add solana-sdk@2.0.3 solana-program-test@2.0.3 tokio --dev && cargo add solana-program@2.0.3` | | +| ✅1.18.26 | `cargo add solana-sdk@1.18.26 solana-program-test@1.18.26 tokio --dev && cargo add solana-program@1.18.26` | | + +这里是 `Cargo.toml` 的例子(对应版本是 `2.0.3`): + +```toml +[package] +name = "hello_world" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +solana-program = "2.0.3" + +[dev-dependencies] +solana-program-test = "2.0.3" +solana-sdk = "2.0.3" +tokio = "1.42.0" +``` + +# 测试 + +关于 solana 程序的测试,我们一般采用 + +bankrun 是一个用于在 Node.js 中测试 Solana 程序的轻量级框架。与传统的 solana-test-validator 相比,bankrun 提供了更高的速度和便利性。它能够实现一些 solana-test-validator 无法做到的功能,例如时间回溯和动态设置账户数据。 + +它会启动一个轻量级的 BanksServer,这个服务类似于一个 RPC 节点,但速度更快,并且创建一个 BanksClient 来与服务器进行通信 + +主要特点: + +- 高效性:比 solana-test-validator 快得多。 +- 灵活性:支持时间回溯和动态账户数据设置。 +- solana-bankrun 底层基于 solana-program-test,使用轻量级的 BanksServer 和 BanksClient。 + +接下来,我们来看看如何用 Rust(`solana-program-test`) 和 NodeJS(`solana-bankrun`) 编写测试用例。 + +## 测试(Rust) + +首先,我们来用 Rust 代码进行测试。 + +首先安装测试所需要的依赖: + +```bash +cargo add solana-sdk@1.18.26 solana-program-test@1.18.26 tokio --dev +# NOTE: There's no error like `Exceeding maximum ...` when building with solana-program = 2.1.4 +# We use solana cli with version `2.1.4` +# To install solana-cli with version 2.1.4, run this command: +# +# sh -c "$(curl -sSfL https://release.anza.xyz/v2.1.4/install)" +# +# cargo add solana-sdk@=2.1.4 solana-program-test@=2.1.4 tokio --dev +# cargo add solana-program@=2.1.4 +``` + +因为我们已经测试过,对于版本 `2.1.4`, `2.0.18`, `2.0.3`, `1.18.26` 都能成功编译和测试,所以我们只选择了其中一个版本 `1.18.26` 来做演示。 + +测试结果输出: + +```bash +(base) dudu@tale ~/Documents/projects/__projects__/solana/projects/hello_world (master)> cargo test-sbf + Compiling hello_world v0.1.0 (/Users/dudu/Documents/projects/__projects__/solana/projects/hello_world) + Finished `release` profile [optimized] target(s) in 2.46s + Blocking waiting for file lock on build directory + Compiling hello_world v0.1.0 (/Users/dudu/Documents/projects/__projects__/solana/projects/hello_world) + Finished `test` profile [unoptimized + debuginfo] target(s) in 14.29s + Running unittests src/lib.rs (target/debug/deps/hello_world-823cf88515d0fd05) + +running 1 test +[2024-12-06T02:00:47.545448000Z INFO solana_program_test] "hello_world" SBF program from /Users/dudu/Documents/projects/__projects__/solana/projects/hello_world/target/deploy/hello_world.so, modified 16 seconds, 964 ms, 380 µs and 220 ns ago +[2024-12-06T02:00:47.750627000Z DEBUG solana_runtime::message_processor::stable_log] Program 1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM invoke [1] +[2024-12-06T02:00:47.750876000Z DEBUG solana_runtime::message_processor::stable_log] Program log: Hello, world! +[2024-12-06T02:00:47.750906000Z DEBUG solana_runtime::message_processor::stable_log] Program 1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM consumed 137 of 200000 compute units +[2024-12-06T02:00:47.750953000Z DEBUG solana_runtime::message_processor::stable_log] Program 1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM success +test test::test_hello_world ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.21s + + Doc-tests hello_world + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +``` + +## 测试(NodeJS) + +接下来,我们来用 NodeJS 编写测试用例。 + +首先使用 pnpm 新建工程。 + +```bash +mkdir hello_world_frontend +cd hello_world_frontend + +# 初始化 pnpm 项目 +pnpm init +``` + +接下来安装依赖: + +```bash +# 安装必要的依赖 +pnpm add -D typescript ts-node @types/node chai ts-mocha solana-bankrun +pnpm add @solana/web3.js solana-bankrun +``` + +然后,编写测试程序: + +```typescript +import { + PublicKey, + Transaction, + TransactionInstruction, +} from "@solana/web3.js"; +import { start } from "solana-bankrun"; +import { describe, test } from "node:test"; +import { assert } from "chai"; + +describe("hello-solana", async () => { + // load program in solana-bankrun + const PROGRAM_ID = PublicKey.unique(); + const context = await start( + [{ name: "hello_world", programId: PROGRAM_ID }], + [] + ); + const client = context.banksClient; + const payer = context.payer; + + test("Say hello!", async () => { + const blockhash = context.lastBlockhash; + // We set up our instruction first. + let ix = new TransactionInstruction({ + // using payer keypair from context to sign the txn + keys: [{ pubkey: payer.publicKey, isSigner: true, isWritable: true }], + programId: PROGRAM_ID, + data: Buffer.alloc(0), // No data + }); + + const tx = new Transaction(); + tx.recentBlockhash = blockhash; + // using payer keypair from context to sign the txn + tx.add(ix).sign(payer); + + // Now we process the transaction + let transaction = await client.processTransaction(tx); + + assert(transaction.logMessages[0].startsWith("Program " + PROGRAM_ID)); + const message = "Program log: " + "Hello, world! GM!GN!"; + console.log("🌈🌈🌈 "); + console.log(transaction.logMessages[1]); + // NOTE: transaction.logMesages is an array: + // + // [ + // 'Program 11111111111111111111111111111112 invoke [1]', + // 'Program log: Hello, world! GM!GN!', + // 'Program 11111111111111111111111111111112 consumed 340 of 200000 compute units', + // 'Program 11111111111111111111111111111112 success' + // ] + assert(transaction.logMessages[1] === message); + assert( + transaction.logMessages[2] === + "Program log: Our program's Program ID: " + PROGRAM_ID + ); + assert( + transaction.logMessages[3].startsWith( + "Program " + PROGRAM_ID + " consumed" + ) + ); + assert(transaction.logMessages[4] === "Program " + PROGRAM_ID + " success"); + assert(transaction.logMessages.length == 5); + }); +}); +``` + +首先,我们通过 `start` 函数生成一个 `context`,这个 `context` 里会有和 `bankServer` 交互的 `bankClient` 以及 `payer` 账户。 + +接下来,通过 `TransactionInstruction` 来准备交易的 `Instruction`,发送交易需要对消息进行签名,这里使用 `payer` 来对交易进行签名,将它放在 `keys` 数组里。 + +```javascript +let ix = new TransactionInstruction({ + keys: [{ pubkey: payer.publicKey, isSigner: true, isWritable: true }], + programId: PROGRAM_ID, + data: Buffer.alloc(0), // No data +}); +``` + +创建一个新的交易指令 (`TransactionInstruction`),`TransactionInstruction` 的定义及参数类型 `TransactionInstructionCtorFields` 如下: + +```typescript +/** + * Transaction Instruction class + */ +declare class TransactionInstruction { + /** + * Public keys to include in this transaction + * Boolean represents whether this pubkey needs to sign the transaction + */ + keys: Array; + /** + * Program Id to execute + */ + programId: PublicKey; + /** + * Program input + */ + data: Buffer; + constructor(opts: TransactionInstructionCtorFields); +} + +/** + * List of TransactionInstruction object fields that may be initialized at construction + */ +type TransactionInstructionCtorFields = { + keys: Array; + programId: PublicKey; + data?: Buffer; +}; +``` + +关于 `TransactionInstructionCtorFields` 的说明: + +- `keys`: 需要签名的公钥(支付者的公钥)。 +- `programId`: 程序的 ID。 +- `data`: 这里没有附加数据。 + +然后我们准备 `Transaction` 的数据。 + +首先 `Transaction` 需要最近的区块哈希,这个可以从 `context` 的 `lastBlockHash` 获取。 + +```javascript +const blockhash = context.lastBlockhash; +``` + +下面是创建交易的过程。 + +```javascript +const tx = new Transaction(); +tx.recentBlockhash = blockhash; +tx.add(ix).sign(payer); +``` + +创建一个新的交易 (`Transaction`) 需要如下步骤: + +- 设置最近的区块哈希。 +- 添加之前定义的指令(`tx.add`),并使用支付者的密钥对交易进行签名(`.sign`)。 + +`add` 函数通过 Javascript 的 [Rest parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters) 特性将参数转换成数组类型,每个数组类型的是 `Transaction | TransactionInstruction | TransactionInstructionCtorFields` 的联合类型 `Union Type`。 + +```typescript +declare class Transaction { + /** + * Signatures for the transaction. Typically created by invoking the + * `sign()` method + */ + signatures: Array; + /** + * The first (payer) Transaction signature + * + * @returns {Buffer | null} Buffer of payer's signature + */ + get signature(): Buffer | null; + /** + * The transaction fee payer + */ + feePayer?: PublicKey; + /** + * The instructions to atomically execute + */ + instructions: Array; + /** + * Add one or more instructions to this Transaction + * + * @param {Array< Transaction | TransactionInstruction | TransactionInstructionCtorFields >} items - Instructions to add to the Transaction + */ + add( + ...items: Array< + Transaction | TransactionInstruction | TransactionInstructionCtorFields + > + ): Transaction; +} +``` + +创建完交易之后,通过 `client.processTransaction` 发送交易并等到结果。 + +```javascript +let transaction = await client.processTransaction(tx); +``` + +这里是 `processTransaction` 的定义: + +```typescript +/** + * A client for the ledger state, from the perspective of an arbitrary validator. + * + * The client is used to send transactions and query account data, among other things. + * Use `start()` to initialize a BanksClient. + */ +export declare class BanksClient { + constructor(inner: BanksClientInner); + private inner; + /** + * Send a transaction and return immediately. + * @param tx - The transaction to send. + */ + sendTransaction(tx: Transaction | VersionedTransaction): Promise; + /** + * Process a transaction and return the result with metadata. + * @param tx - The transaction to send. + * @returns The transaction result and metadata. + */ + processTransaction( + tx: Transaction | VersionedTransaction + ): Promise; +} +``` + +其 `inner` 是个 `BanksClient`,除了处理交易外,它还能干很多事情,以下是它的定义。 + +```typescript +export class BanksClient { + getAccount(address: Uint8Array, commitment?: CommitmentLevel | undefined | null): Promise + sendLegacyTransaction(txBytes: Uint8Array): Promise + sendVersionedTransaction(txBytes: Uint8Array): Promise + processLegacyTransaction(txBytes: Uint8Array): Promise + processVersionedTransaction(txBytes: Uint8Array): Promise + tryProcessLegacyTransaction(txBytes: Uint8Array): Promise + tryProcessVersionedTransaction(txBytes: Uint8Array): Promise + simulateLegacyTransaction(txBytes: Uint8Array, commitment?: CommitmentLevel | undefined | null): Promise + simulateVersionedTransaction(txBytes: Uint8Array, commitment?: CommitmentLevel | undefined | null): Promise + getTransactionStatus(signature: Uint8Array): Promise + getTransactionStatuses(signatures: Array): Promise> + getSlot(commitment?: CommitmentLevel | undefined | null): Promise + getBlockHeight(commitment?: CommitmentLevel | undefined | null): Promise + getRent(): Promise + getClock(): Promise + getBalance(address: Uint8Array, commitment?: CommitmentLevel | undefined | null): Promise + getLatestBlockhash(commitment?: CommitmentLevel | undefined | null): Promise + getFeeForMessage(messageBytes: Uint8Array, commitment?: CommitmentLevel | undefined | null): Promise +} + +/** + * Process a transaction and return the result with metadata. + * @param tx - The transaction to send. + * @returns The transaction result and metadata. + */ + async processTransaction( + tx: Transaction | VersionedTransaction, + ): Promise { + const serialized = tx.serialize(); + const internal = this.inner; + const inner = + tx instanceof Transaction + ? await internal.processLegacyTransaction(serialized) + : await internal.processVersionedTransaction(serialized); + return new BanksTransactionMeta(inner); + } +``` + +`processTransaction` 会先通过 `serialize` 对 transaction 进行序列化,判断属于 `LegacyTransaction` 还是 `VersionedTransaction`,分别调用 `processLegacyTransaction` 或 `processVersionedTransaction` 异步方法,并将结果通过 `BanksTransactionMeta` 返回。 + +而 `BanksTransactionMeta` 包含了 `logMessages` `returnData` 和 `computeUnitsConsumed` 属性。 + +```typescript +export class TransactionReturnData { + get programId(): Uint8Array; + get data(): Uint8Array; +} +export class BanksTransactionMeta { + get logMessages(): Array; + get returnData(): TransactionReturnData | null; + get computeUnitsConsumed(): bigint; +} +``` + +其中 `logMessages` 是一个字符串数组,用于存储与交易相关的日志消息。我们可以通过这些日志信息,对测试结果进行验证。 + +比如,可以通过对 `logMessages[0]` 验证 solana program 被调用时,会输出以 `Program ` + `PROGRAM_ID` 开头的内容: + +```javascript +assert(transaction.logMessages[0].startsWith("Program " + PROGRAM_ID)); +``` + +一个简单的 `logMessages` 数组的例子: + +```json +[ + "Program 11111111111111111111111111111112 invoke [1]", + "Program log: Hello, world! GM!GN!", + "Program log: Our program's Program ID: {program_id}", + "Program 11111111111111111111111111111112 consumed 443 of 200000 compute units", + "Program 11111111111111111111111111111112 success" +] +``` + +值得注意的是,在我们的 solana program 里,第一个 `msg!` 输出的日志是 `Hello, world! GM!GN!`,但是发送交易返回的 `logMessages` 数组里它在数组的第二个元素,这是什么原因呢? + +```rust +pub fn process_instruction( + program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + msg!("Hello, world! GM!GN!"); + // NOTE: You must not use interpolating string like this, as it will not + // output the string value correctly. + // + // You must use placeholder instead. + // + // Below is the transaction.logMessages array when using interpolating string + // + // [ + // 'Program 11111111111111111111111111111112 invoke [1]', + // 'Program log: Hello, world! GM!GN!', + // "Program log: Our program's Program ID: {program_id}", + // 'Program 11111111111111111111111111111112 consumed 443 of 200000 compute units', + // 'Program 11111111111111111111111111111112 success' + // ] + // msg!("Our program's Program ID: {program_id}"); + msg!("Our program's Program ID: {}", program_id); + Ok(()) +} +``` + +其原因是 solana program 执行时 `program runtime` 会通过 `program_invoke` 函数打印被调用的日志,也就是这里的: `Program 11111111111111111111111111111112 invoke [1]`。关于 `program_invoke` 函数的代码可以在 [anza-xyz/agave](https://github.com/anza-xyz/agave/blob/6c6c26eec4317e06e334609ea686b0192a210092/program-runtime/src/stable_log.rs#L20) 这里找到。 + +````rust +/// Log a program invoke. +/// +/// The general form is: +/// +/// ```notrust +/// "Program
invoke []" +/// ``` +pub fn program_invoke( + log_collector: &Option>>, + program_id: &Pubkey, + invoke_depth: usize, +) { + ic_logger_msg!( + log_collector, + "Program {} invoke [{}]", + program_id, + invoke_depth + ); +} +```` + +接下来的检查可以根据具体的业务场景按部就班的进行。 + +比如,下面检查 solana program 里第一个 `msg!` 打印的内容: + +```javascript +const message = "Program log: " + "Hello, world! GM!GN!"; +assert(transaction.logMessages[1] === message); +``` + +接下来,检查 solana program 里第二个 `msg!` 打印的内容: + +```javascript +assert(transaction.logMessages[1] === message); +assert( + transaction.logMessages[2] === + "Program log: Our program's Program ID: " + PROGRAM_ID +); +``` + +再下来,检查其他日志消息的内容和格式,包括程序的成功消息和消耗的计算单位,并确保日志消息的总数为 `5`。 + +```javascript +assert( + transaction.logMessages[3].startsWith("Program " + PROGRAM_ID + " consumed") +); +assert(transaction.logMessages[4] === "Program " + PROGRAM_ID + " success"); +assert(transaction.logMessages.length == 5); +``` + +至此,一个简单的通过 `NodeJS` 编写的测试就写好了。 + +#### All in one test setup script + +如果你比较懒,可以直接运行以下脚本到 `setup.sh`,并运行 `bash setup.sh`。 + +```bash +# 创建测试目录 +mkdir hello_world_frontend +cd hello_world_frontend + +# 初始化 pnpm 项目 +pnpm init + +# 安装必要的依赖 +pnpm add -D typescript ts-node @types/node chai ts-mocha solana-bankrun +pnpm add @solana/web3.js solana-bankrun + +# 创建 TypeScript 配置文件 +cat > tsconfig.json << EOF +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} +EOF + +# 创建源代码目录和测试文件 +mkdir -p tests +cat > tests/hello_world.test.ts << EOF +import { + PublicKey, + Transaction, + TransactionInstruction, + } from "@solana/web3.js"; + import { start } from "solana-bankrun"; + import { describe, test } from "node:test"; + import { assert } from "chai"; + + describe("hello-solana", async () => { + // load program in solana-bankrun + const PROGRAM_ID = PublicKey.unique(); + const context = await start( + [{ name: "hello_world", programId: PROGRAM_ID }], + [], + ); + const client = context.banksClient; + const payer = context.payer; + + test("Say hello!", async () => { + const blockhash = context.lastBlockhash; + // We set up our instruction first. + let ix = new TransactionInstruction({ + // using payer keypair from context to sign the txn + keys: [{ pubkey: payer.publicKey, isSigner: true, isWritable: true }], + programId: PROGRAM_ID, + data: Buffer.alloc(0), // No data + }); + + const tx = new Transaction(); + tx.recentBlockhash = blockhash; + // using payer keypair from context to sign the txn + tx.add(ix).sign(payer); + + // Now we process the transaction + let transaction = await client.processTransaction(tx); + + assert(transaction.logMessages[0].startsWith("Program " + PROGRAM_ID)); + const message = "Program log: " + "Hello, world! GM!GN!"; + console.log("🌈🌈🌈 "); + console.log(transaction.logMessages); + assert(transaction.logMessages[1] === message); + assert( + transaction.logMessages[2] === + "Program log: Our program's Program ID: " + PROGRAM_ID, + ); + assert( + transaction.logMessages[3].startsWith( + "Program " + PROGRAM_ID + " consumed", + ), + ); + assert(transaction.logMessages[4] === "Program " + PROGRAM_ID + " success"); + assert(transaction.logMessages.length == 5); + }); +}); +EOF + +# 更新 package.json 添加测试脚本 +cat > package.json << EOF +{ + "name": "hello_world_frontend", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "pnpm ts-mocha -p ./tsconfig.json -t 1000000 ./tests/hello_world.test.ts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/jest": "^29.5.11", + "@types/node": "^20.10.5", + "chai": "^5.1.2", + "jest": "^29.7.0", + "solana-bankrun": "^0.4.0", + "ts-jest": "^29.1.1", + "ts-mocha": "^10.0.0", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + }, + "dependencies": { + "@solana/web3.js": "^1.87.6" + } +} + +# 运行测试 +pnpm test +EOF +``` + +# TODO + +这个命令会在 `target/deploy` 目录下生成两个重要文件: + +- `hello_world.so`:编译后的程序文件,这是一个 BPF (Berkeley Packet Filter) 格式的可执行文件 +- `hello_world-keypair.json`:程序的密钥对文件,用于程序的部署和升级 + +构建过程可能需要一些时间,因为它需要: + +1. 下载并编译必要的依赖 +2. 将 Rust 代码编译成 BPF 字节码 +3. 生成程序需要的密钥对 + +如果你看到类似下面的输出,说明构建成功: + +```bash +BPF SDK: /Users/username/.local/share/solana/install/releases/1.14.x/solana-release/bin/sdk/bpf +cargo-build-sbf child: rustup toolchain list -v +cargo-build-sbf child: cargo +bpf build --target bpfel-unknown-unknown --release + Finished release [optimized] target(s) in 0.20s +cargo-build-sbf child: /Users/username/.local/share/solana/install/releases/1.14.x/solana-release/bin/sdk/bpf/scripts/strip.sh /Users/username/projects/hello_world/target/bpfel-unknown-unknown/release/hello_world.so /Users/username/projects/hello_world/target/deploy/hello_world.so +``` + +# 部署程序 + +现在我们可以将编译好的程序部署到 Solana 网络上。在开发阶段,我们通常使用本地测试网(localhost)或开发网(devnet)进行测试。 + +首先确保你的 Solana CLI 配置指向了正确的集群: + +```bash +# 切换到开发网 +solana config set --url devnet + +# 查看当前配置 +solana config get +``` + +然后使用以下命令部署程序: + +```bash +solana program deploy target/deploy/hello_world.so +``` + +部署成功后,你会看到程序的 ID(公钥地址)。请保存这个地址,因为在后续与程序交互时会需要它。 + +# 下一步 + +至此,我们已经完成了一个最基础的 Solana 程序的开发和部署。虽然这个程序只是简单地打印 "Hello, world!",但它包含了 Solana 程序开发的基本要素: + +- 程序入口点的定义 +- 基本的参数结构 +- 构建和部署流程 + +在接下来的章节中,我们将学习: + +- 如何处理账户数据 +- 如何实现更复杂的指令逻辑 +- 如何进行程序测试 +- 如何确保程序安全性 + +# Refs + +关于 cargo-build-sbf 解释 +https://github.com/solana-labs/solana/issues/34987#issuecomment-1913538260 + +https://solana.stackexchange.com/questions/16443/error-function-stack-offset-of-7256-exceeded-max-offset-of-4096-by-3160-bytes + +安装 solana cli tool suites(注意不要安装 edge 版本,会发现部署不成功问题) +https://solana.com/docs/intro/installation + +https://github.com/solana-labs/solana/issues/34987#issuecomment-1914665002 +https://github.com/anza-xyz/agave/issues/1572 + +在 solana 编写一个 helloworld +https://solana.com/developers/guides/getstarted/local-rust-hello-world#create-a-new-rust-library-with-cargo diff --git "a/src/solana/quickstart/\344\275\277\347\224\250TypeScript\345\210\233\345\273\272\350\264\246\346\210\267.md" "b/src/solana/quickstart/\344\275\277\347\224\250TypeScript\345\210\233\345\273\272\350\264\246\346\210\267.md" new file mode 100644 index 0000000..2198cb6 --- /dev/null +++ "b/src/solana/quickstart/\344\275\277\347\224\250TypeScript\345\210\233\345\273\272\350\264\246\346\210\267.md" @@ -0,0 +1 @@ +# 使用 TypeScript 创建账户 diff --git a/src/terraform/gitlab/start-gitlab-using-terraform.md b/src/terraform/gitlab/start-gitlab-using-terraform.md new file mode 100644 index 0000000..8eb8913 --- /dev/null +++ b/src/terraform/gitlab/start-gitlab-using-terraform.md @@ -0,0 +1,890 @@ +# Start gitlab using terraform + +- [aws](#aws) + - [aws provider](#aws-provider) + - [vpc](#vpc) +- [ec2 instance](#ec2-instance) +- [security group](#security-group) +- [ebs volume](#ebs-volume) +- [eip](#eip) +- [gitlab setup](#gitlab-setup) +- [GitLab clone](#gitlab-clone) +- [GitLab administration](#gitlab-administration) + +如何在 `AWS` 中快速部署一台 `GitLab` 服务?利用 `terraform`,我们可以自由调配云服务的资源,并快速将 `GitLab` 部署到 `AWS` 中。 + +# aws + +## aws provider + +在我们使用 `terraform` 创建云服务器时,我们需要指定 provider。 + +首先,我们定义 aws provider。 + +aws provider 是 `terraform` 的一个特殊的资源类型,它提供了一个简单的 API,用于访问 `AWS` 中的服务。 + +EN: Before we can use terraform to deploy our application, we need to install the aws provider. + +以下配置是 aws provider 的配置: + +```hcl +provider "aws" { + region = "cn-northwest-1" +} +``` + +我们指定了 `AWS` 的 `region` 为 `cn-northwest-1`,这个 `region` 就是我们的云服务器所在的地区。 + +## vpc + +运行 `GitLab` 的 EC2 放在哪里呢?一般来讲,服务器需要一个 `VPC`,这个 `VPC` 就是我们的云服务器所在的网络,而我们的 EC2 instance 就是在这个 `VPC` 中的一个虚拟机。 + +一般我们会利用现有的 vpc 而不是新建 vpc,从 AWS console 中查询以下 vpc 的 id,我们把它放到名为 vpc 的 variable 中。 + +EN: As we have already have a vpc, we can use it to deploy our application. Here we use variable to define the vpc id. + +```hcl +variable "vpc" { + type = string + default = "vpc-0f0f0f0f0f0f0f0f" + description = "The VPC ID of " +} +``` + +# ec2 instance + +在确定了 `VPC` 的 id 之后,我们就可以创建 EC2 instance 了。 + +我们使用 `aws_instance` 资源来创建 EC2 instance,并且指定了一个名为 `gitlab` 的实例。 + +此外,我们还指定了一个实例的类型,这个类型是 `m5a.large`,这个类型是 `AWS` 中的一个预定义的类型,我们可以在 AWS console 中查询到。 + +`ami` 为版本为 Ubuntu 20.04 的镜像,`key_name` 为登录 EC2 的 key,`root_block_device` 为系统盘,我们此时分配了 `40G` 的磁盘空间,`subnet_id` 为 vpc 下的一个子网,它也是预先就创建好的。 + +`vpc_security_group_ids` 为我们创建 EC2 所需的安全组,下面我们会讲它开启了哪些规则。 + +EN: We can use the ec2 resource to deploy our application. We use `aws_instance` resource and specify the vpc id, subnet id, instance type(`m5a.large` ), key_name(`gitlab`), Ubuntu 20.04 server(`ami-ffff111db56e65f8d`), 40GiB root block size volume. + +```hcl +resource "aws_instance" "gitlab" { + # Ubuntu Server 20.04 LTS (HVM), SSD Volume Type - ami-ffff111db56e65f8d (64-bit x86) / ami-0429c857c8db3027a (64-bit Arm) + ami = "ami-ffff111db56e65f8d" + instance_type = "m5a.large" + key_name = "gitlab" + + root_block_device { + volume_size = "40" + volume_type = "gp3" + } + + # (subnet-public1-cn-north-1a) + subnet_id = "subnet-2222333344445555" + vpc_security_group_ids = ["${aws_security_group.gitlab.id}"] + # associate_public_ip_address = true + tags = { + Name = "gitlab" + } +} +``` + +在 EC2 创建好之后,我们可以这样登录: + +EN: After provisioning, you can access the GitLab instance. + +``` +ssh -i ~/.ssh/gitlab.pem +``` + +# security group + +EC2 实例需要一个安全组,用来控制它的 `ingress` 规则和 `egress` 规则。可以把 security group 看成是防火墙。 + +通过配置文件可知,我们允许 ssh 登录 EC2,允许访问 `GitLab` http 的端口 80 和 https 的端口 443。 + +对于出口的规则,我们不做任何限制,可以访问任何地方。 + +```hcl +resource "aws_security_group" "gitlab" { + description = "Security group for gitlab" + vpc_id = var.vpc + + egress { + cidr_blocks = ["0.0.0.0/0"] + description = "Allow all outbound traffic" + from_port = 0 + protocol = "-1" + self = false + to_port = 0 + } + + ingress { + cidr_blocks = ["0.0.0.0/0"] + description = "ssh" + from_port = 22 + protocol = "tcp" + self = false + to_port = 22 + } + + ingress { + cidr_blocks = ["0.0.0.0/0"] + description = "allow public access http" + from_port = 80 + protocol = "tcp" + self = false + to_port = 80 + } + + ingress { + cidr_blocks = ["0.0.0.0/0"] + description = "allow public access https" + from_port = 443 + protocol = "tcp" + self = false + to_port = 443 + } + + name = "gitlab" + revoke_rules_on_delete = false + tags = { + "Name" = "gitlab" + } + tags_all = { + "Name" = "gitlab" + } + + timeouts {} +} +``` + +# ebs volume + +为了存储 git repository,我们可以利用 `aws_ebs_volume` 给 EC2 实例分配一个 EBS 磁盘,并且指定它的大小为 `40GiB`,再通过 `aws_volume_attachment` 将 EBS 磁盘与 EC2 实例进行绑定。 + +```hcl +resource "aws_ebs_volume" "gitlab" { + availability_zone = "cn-north-1a" + size = 40 + type = "gp3" + + tags = { + Name = "gitlab" + } +} + +resource "aws_volume_attachment" "ebs_attachment_gitlab" { + device_name = "/dev/sdh" + volume_id = aws_ebs_volume.gitlab.id + instance_id = aws_instance.gitlab.id +} +``` + +好了,就这样就差不都了,我们来执行一下 `terraform plan` 命令,看看有没有错误。 + +下面是添加了 `aws_ebs_volume` 和 `aws_volume_attachment` 之后执行 `terraform plan` 的结果: + +``` +Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + + create + +Terraform will perform the following actions: + + # aws_ebs_volume.gitlab will be created + + resource "aws_ebs_volume" "gitlab" { + + arn = (known after apply) + + availability_zone = "cn-north-1a" + + encrypted = (known after apply) + + id = (known after apply) + + iops = (known after apply) + + kms_key_id = (known after apply) + + size = 40 + + snapshot_id = (known after apply) + + tags = { + + "Name" = "gitlab" + } + + tags_all = { + + "Name" = "gitlab" + } + + throughput = (known after apply) + + type = "gp3" + } + + # aws_instance.gitlab will be created + + resource "aws_instance" "gitlab" { + + ami = "ami-ffff111db56e65f8d" + + arn = (known after apply) + + associate_public_ip_address = (known after apply) + + availability_zone = (known after apply) + + cpu_core_count = (known after apply) + + cpu_threads_per_core = (known after apply) + + disable_api_termination = (known after apply) + + ebs_optimized = (known after apply) + + get_password_data = false + + host_id = (known after apply) + + id = (known after apply) + + instance_initiated_shutdown_behavior = (known after apply) + + instance_state = (known after apply) + + instance_type = "m5a.large" + + ipv6_address_count = (known after apply) + + ipv6_addresses = (known after apply) + + key_name = "gitlab" + + monitoring = (known after apply) + + outpost_arn = (known after apply) + + password_data = (known after apply) + + placement_group = (known after apply) + + placement_partition_number = (known after apply) + + primary_network_interface_id = (known after apply) + + private_dns = (known after apply) + + private_ip = (known after apply) + + public_dns = (known after apply) + + public_ip = (known after apply) + + secondary_private_ips = (known after apply) + + security_groups = (known after apply) + + source_dest_check = true + + subnet_id = "subnet-88ff88ff88ff" + + tags = { + + "Name" = "gitlab" + } + + tags_all = { + + "Name" = "gitlab" + } + + tenancy = (known after apply) + + user_data = (known after apply) + + user_data_base64 = (known after apply) + + user_data_replace_on_change = false + + vpc_security_group_ids = (known after apply) + + + capacity_reservation_specification { + + capacity_reservation_preference = (known after apply) + + + capacity_reservation_target { + + capacity_reservation_id = (known after apply) + } + } + + + ebs_block_device { + + delete_on_termination = (known after apply) + + device_name = (known after apply) + + encrypted = (known after apply) + + iops = (known after apply) + + kms_key_id = (known after apply) + + snapshot_id = (known after apply) + + tags = (known after apply) + + throughput = (known after apply) + + volume_id = (known after apply) + + volume_size = (known after apply) + + volume_type = (known after apply) + } + + + enclave_options { + + enabled = (known after apply) + } + + + ephemeral_block_device { + + device_name = (known after apply) + + no_device = (known after apply) + + virtual_name = (known after apply) + } + + + metadata_options { + + http_endpoint = (known after apply) + + http_put_response_hop_limit = (known after apply) + + http_tokens = (known after apply) + + instance_metadata_tags = (known after apply) + } + + + network_interface { + + delete_on_termination = (known after apply) + + device_index = (known after apply) + + network_interface_id = (known after apply) + } + + + root_block_device { + + delete_on_termination = true + + device_name = (known after apply) + + encrypted = (known after apply) + + iops = (known after apply) + + kms_key_id = (known after apply) + + throughput = (known after apply) + + volume_id = (known after apply) + + volume_size = 60 + + volume_type = "standard" + } + } + + # aws_security_group.gitlab will be created + + resource "aws_security_group" "gitlab" { + + arn = (known after apply) + + description = "Security group for gitlab" + + egress = [ + + { + + cidr_blocks = [ + + "0.0.0.0/0", + ] + + description = "Allow all outbound traffic" + + from_port = 0 + + ipv6_cidr_blocks = [] + + prefix_list_ids = [] + + protocol = "-1" + + security_groups = [] + + self = false + + to_port = 0 + }, + ] + + id = (known after apply) + + ingress = [ + + { + + cidr_blocks = [ + + "0.0.0.0/0", + ] + + description = "allow public access http" + + from_port = 80 + + ipv6_cidr_blocks = [] + + prefix_list_ids = [] + + protocol = "tcp" + + security_groups = [] + + self = false + + to_port = 80 + }, + + { + + cidr_blocks = [ + + "0.0.0.0/0", + ] + + description = "allow public access https" + + from_port = 443 + + ipv6_cidr_blocks = [] + + prefix_list_ids = [] + + protocol = "tcp" + + security_groups = [] + + self = false + + to_port = 443 + }, + + { + + cidr_blocks = [ + + "0.0.0.0/0", + ] + + description = "test" + + from_port = 22 + + ipv6_cidr_blocks = [] + + prefix_list_ids = [] + + protocol = "tcp" + + security_groups = [] + + self = false + + to_port = 22 + }, + + { + + cidr_blocks = [ + + "10.0.0.0/16", + ] + + description = "allow ssh" + + from_port = 22 + + ipv6_cidr_blocks = [] + + prefix_list_ids = [] + + protocol = "tcp" + + security_groups = [] + + self = false + + to_port = 22 + }, + ] + + name = "gitlab" + + name_prefix = (known after apply) + + owner_id = (known after apply) + + revoke_rules_on_delete = false + + tags = { + + "Name" = "gitlab" + } + + tags_all = { + + "Name" = "gitlab" + } + + vpc_id = "vpc-0afafafafaf" + + + timeouts {} + } + + # aws_volume_attachment.ebs_attachment_gitlab will be created + + resource "aws_volume_attachment" "ebs_attachment_gitlab" { + + device_name = "/dev/sdh" + + id = (known after apply) + + instance_id = (known after apply) + + volume_id = (known after apply) + } + +Plan: 4 to add, 0 to change, 0 to destroy. + +Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. +``` + +以上显示,terraform 会创建以下 4 种资源: + +- `aws_instance.gitlab` +- `aws_volume.gitlab` +- `aws_volume_attachment.ebs_attachment_gitlab` +- `aws_security_group.gitlab` + +下面执行 `terraform apply` 命令,4 个资源都会被创建,并且资源的属性都会被设置为预期的值。 + +``` + Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + + create + + Terraform will perform the following actions: + + # aws_ebs_volume.gitlab will be created + + resource "aws_ebs_volume" "gitlab" { + + arn = (known after apply) + + availability_zone = "cn-north-1a" + + encrypted = (known after apply) + + id = (known after apply) + + iops = (known after apply) + + kms_key_id = (known after apply) + + size = 40 + + snapshot_id = (known after apply) + + tags = { + + "Name" = "gitlab" + } + + tags_all = { + + "Name" = "gitlab" + } + + throughput = (known after apply) + + type = "gp3" + } + + # aws_instance.gitlab will be created + + resource "aws_instance" "gitlab" { + + ami = "ami-ffff111db56e65f8d" + + arn = (known after apply) + + associate_public_ip_address = (known after apply) + + availability_zone = (known after apply) + + cpu_core_count = (known after apply) + + cpu_threads_per_core = (known after apply) + + disable_api_termination = (known after apply) + + ebs_optimized = (known after apply) + + get_password_data = false + + host_id = (known after apply) + + id = (known after apply) + + instance_initiated_shutdown_behavior = (known after apply) + + instance_state = (known after apply) + + instance_type = "m5a.large" + + ipv6_address_count = (known after apply) + + ipv6_addresses = (known after apply) + + key_name = "gitlab" + + monitoring = (known after apply) + + outpost_arn = (known after apply) + + password_data = (known after apply) + + placement_group = (known after apply) + + placement_partition_number = (known after apply) + + primary_network_interface_id = (known after apply) + + private_dns = (known after apply) + + private_ip = (known after apply) + + public_dns = (known after apply) + + public_ip = (known after apply) + + secondary_private_ips = (known after apply) + + security_groups = (known after apply) + + source_dest_check = true + + subnet_id = "subnet-069a0f9b9c9f9f9f9" + + tags = { + + "Name" = "gitlab" + } + + tags_all = { + + "Name" = "gitlab" + } + + tenancy = (known after apply) + + user_data = (known after apply) + + user_data_base64 = (known after apply) + + user_data_replace_on_change = false + + vpc_security_group_ids = (known after apply) + + + capacity_reservation_specification { + + capacity_reservation_preference = (known after apply) + + + capacity_reservation_target { + + capacity_reservation_id = (known after apply) + } + } + + + ebs_block_device { + + delete_on_termination = (known after apply) + + device_name = (known after apply) + + encrypted = (known after apply) + + iops = (known after apply) + + kms_key_id = (known after apply) + + snapshot_id = (known after apply) + + tags = (known after apply) + + throughput = (known after apply) + + volume_id = (known after apply) + + volume_size = (known after apply) + + volume_type = (known after apply) + } + + + enclave_options { + + enabled = (known after apply) + } + + + ephemeral_block_device { + + device_name = (known after apply) + + no_device = (known after apply) + + virtual_name = (known after apply) + } + + + metadata_options { + + http_endpoint = (known after apply) + + http_put_response_hop_limit = (known after apply) + + http_tokens = (known after apply) + + instance_metadata_tags = (known after apply) + } + + + network_interface { + + delete_on_termination = (known after apply) + + device_index = (known after apply) + + network_interface_id = (known after apply) + } + + + root_block_device { + + delete_on_termination = true + + device_name = (known after apply) + + encrypted = (known after apply) + + iops = (known after apply) + + kms_key_id = (known after apply) + + throughput = (known after apply) + + volume_id = (known after apply) + + volume_size = 60 + + volume_type = "standard" + } + } + + # aws_security_group.gitlab will be created + + resource "aws_security_group" "gitlab" { + + arn = (known after apply) + + description = "Security group for gitlab" + + egress = [ + + { + + cidr_blocks = [ + + "0.0.0.0/0", + ] + + description = "Allow all outbound traffic" + + from_port = 0 + + ipv6_cidr_blocks = [] + + prefix_list_ids = [] + + protocol = "-1" + + security_groups = [] + + self = false + + to_port = 0 + }, + ] + + id = (known after apply) + + ingress = [ + + { + + cidr_blocks = [ + + "0.0.0.0/0", + ] + + description = "allow public access http" + + from_port = 80 + + ipv6_cidr_blocks = [] + + prefix_list_ids = [] + + protocol = "tcp" + + security_groups = [] + + self = false + + to_port = 80 + }, + + { + + cidr_blocks = [ + + "0.0.0.0/0", + ] + + description = "allow public access https" + + from_port = 443 + + ipv6_cidr_blocks = [] + + prefix_list_ids = [] + + protocol = "tcp" + + security_groups = [] + + self = false + + to_port = 443 + }, + + { + + cidr_blocks = [ + + "0.0.0.0/0", + ] + + description = "test" + + from_port = 22 + + ipv6_cidr_blocks = [] + + prefix_list_ids = [] + + protocol = "tcp" + + security_groups = [] + + self = false + + to_port = 22 + }, + + { + + cidr_blocks = [ + + "10.0.0.0/16", + ] + + description = "allow ssh" + + from_port = 22 + + ipv6_cidr_blocks = [] + + prefix_list_ids = [] + + protocol = "tcp" + + security_groups = [] + + self = false + + to_port = 22 + }, + ] + + name = "gitlab" + + name_prefix = (known after apply) + + owner_id = (known after apply) + + revoke_rules_on_delete = false + + tags = { + + "Name" = "gitlab" + } + + tags_all = { + + "Name" = "gitlab" + } + + vpc_id = "vpc-02fefefefefe" + + + timeouts {} + } + + # aws_volume_attachment.ebs_attachment_gitlab will be created + + resource "aws_volume_attachment" "ebs_attachment_gitlab" { + + device_name = "/dev/sdh" + + id = (known after apply) + + instance_id = (known after apply) + + volume_id = (known after apply) + } + + Plan: 4 to add, 0 to change, 0 to destroy. + + Do you want to perform these actions? + Terraform will perform the actions described above. + Only 'yes' will be accepted to approve. + + Enter a value: yes +``` + +输入 yes 之后,我们的 4 种资源都会被创建好了。 + +``` +aws_instance.gitlab: Creating... +aws_instance.gitlab: Still creating... [10s elapsed] +aws_instance.gitlab: Creation complete after 13s [id=i-0afefefefe] +aws_volume_attachment.ebs_attachment_gitlab: Creating... +aws_volume_attachment.ebs_attachment_gitlab: Still creating... [10s elapsed] +aws_volume_attachment.ebs_attachment_gitlab: Still creating... [20s elapsed] +aws_volume_attachment.ebs_attachment_gitlab: Creation complete after 21s [id=vai-28fefefe] + +Apply complete! Resources: 4 added, 0 changed, 0 destroyed. +``` + +# eip + +下面我们给 gitlab 机器分配一个 `EIP`,它是一个 public ip 地址,我们可以通过这个 `EIP` 来访问 gitlab 机器。 + +EN: Add public ip for ec2 instance + +```hcl +resource "aws_eip" "gitlab" { + vpc = true + tags = { + Name = "gitlab" + } +} + +resource "aws_eip_association" "eip_association_gitlab" { + instance_id = aws_instance.gitlab.id + allocation_id = aws_eip.gitlab.id +} +``` + +执行 `terraform plan` 后,我们会看到下面的输出: + +``` +# aws_eip.gitlab will be created + + resource "aws_eip" "gitlab" { + + allocation_id = (known after apply) + + association_id = (known after apply) + + carrier_ip = (known after apply) + + customer_owned_ip = (known after apply) + + domain = (known after apply) + + id = (known after apply) + + instance = (known after apply) + + network_border_group = (known after apply) + + network_interface = (known after apply) + + private_dns = (known after apply) + + private_ip = (known after apply) + + public_dns = (known after apply) + + public_ip = (known after apply) + + public_ipv4_pool = (known after apply) + + tags = { + + "Name" = "gitlab" + } + + tags_all = { + + "Name" = "gitlab" + } + + vpc = true + } + + # aws_eip_association.eip_association_gitlab will be created + + resource "aws_eip_association" "eip_association_gitlab" { + + allocation_id = (known after apply) + + id = (known after apply) + + instance_id = (known after apply) + + network_interface_id = (known after apply) + + private_ip_address = (known after apply) + + public_ip = (known after apply) + } +``` + +它会创建两个资源:一个 `EIP` 和一个 `EIP` 关联,这样,我们的 gitlab 机器就可以通过这个 `EIP` 访问了。 + +``` +Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + + create + +Terraform will perform the following actions: + + # aws_eip.gitlab will be created + + resource "aws_eip" "gitlab" { + + allocation_id = (known after apply) + + association_id = (known after apply) + + carrier_ip = (known after apply) + + customer_owned_ip = (known after apply) + + domain = (known after apply) + + id = (known after apply) + + instance = (known after apply) + + network_border_group = (known after apply) + + network_interface = (known after apply) + + private_dns = (known after apply) + + private_ip = (known after apply) + + public_dns = (known after apply) + + public_ip = (known after apply) + + public_ipv4_pool = (known after apply) + + tags = { + + "Name" = "gitlab" + } + + tags_all = { + + "Name" = "gitlab" + } + + vpc = true + } + + # aws_eip_association.eip_association_gitlab will be created + + resource "aws_eip_association" "eip_association_gitlab" { + + allocation_id = (known after apply) + + id = (known after apply) + + instance_id = "i-0afefefefe" + + network_interface_id = (known after apply) + + private_ip_address = (known after apply) + + public_ip = (known after apply) + } + +Plan: 2 to add, 0 to change, 0 to destroy. + +Do you want to perform these actions? + Terraform will perform the actions described above. + Only 'yes' will be accepted to approve. + + Enter a value: yes + +aws_eip.gitlab: Creating... +aws_eip.gitlab: Creation complete after 0s [id=eipalloc-ppp-kkk-0xff0xff] +aws_eip_association.eip_association_gitlab: Creating... +aws_eip_association.eip_association_gitlab: Creation complete after 1s [id=eipassoc-kukukulala] + +Apply complete! Resources: 2 added, 0 changed, 0 destroyed. +``` + +# gitlab setup + +在结束了基础设施的创建之后,我们需要安装及设置 gitlab 软件本身。 + +我们使用 https://github.com/sameersbn/docker-gitlab 这个作为 `GitLab` 的镜像来安装 gitlab。 + +准备 `docker-compose.yml` 文件,运行 `docker-compose up -d` 命令,这样我们就可以看到 gitlab 在运行了。 + +```yaml +version: "2.3" + +services: + redis: + restart: always + image: redis:6.2.6 + command: + - --loglevel warning + volumes: + - ./redis-data:/data:Z + + postgresql: + restart: always + image: sameersbn/postgresql:12-20200524 + volumes: + - ./postgresql-data:/var/lib/postgresql:Z + environment: + - DB_USER=gitlab + - DB_PASS=password + - DB_NAME=gitlabhq_production + - DB_EXTENSION=pg_trgm,btree_gist + + gitlab: + restart: always + image: sameersbn/gitlab:14.9.2 + depends_on: + - redis + - postgresql + ports: + - "10080:80" + # - "443:443" + - "10022:22" + volumes: + - ./gitlab-data:/home/git/data:Z + healthcheck: + test: ["CMD", "/usr/local/sbin/healthcheck"] + interval: 5m + timeout: 10s + retries: 3 + start_period: 5m + environment: + - DEBUG=true + + - DB_ADAPTER=postgresql + - DB_HOST=postgresql + - DB_PORT=5432 + - DB_USER=gitlab + - DB_PASS=password + - DB_NAME=gitlabhq_production + + - REDIS_HOST=redis + - REDIS_PORT=6379 + + - GITLAB_HTTPS=false + #- SSL_SELF_SIGNED=true + + - GITLAB_HOST= + #- GITLAB_PORT=443 + - GITLAB_SSH_PORT=10022 + - GITLAB_RELATIVE_URL_ROOT= + - GITLAB_SECRETS_DB_KEY_BASE=FF11111 + - GITLAB_SECRETS_SECRET_KEY_BASE=FF22222 + - GITLAB_SECRETS_OTP_KEY_BASE=FF33333 + +volumes: + redis-data: + postgresql-data: + gitlab-data: +``` + +# GitLab clone + +因为我们在 `docker-compose.yml` 中指定了 `10022:22` 这样的端口映射,在克隆代码时,所以我们需要指定端口信息: + +```shell +git clone ssh://git@:10022//.git +``` + +# GitLab administration + +`gitlab` 运行之后,我们可以对 `gitlab` 进行管理以增加其安全性,比如: + +- 开启两步认证 `MFA` +- 禁用注册功能,只允许用户通过邮箱登录,取消勾选 `Menu > Admin > Settings > General > Sign-up restrictions > Sign-up enabled` +- 进入 `Menu > Admin > Settings > General > Visibility and access controls section`,限制 `Restricted visibility levels` 为 `public`,这样可以限制只有登录用户才能查看 user profile +- 取消用户注册,设置 `Sign-up restrictions` 为 `disabled` +- 限制两步认证才能登录,勾选 `Sign-in restrictions > Two-factor authentication > Enforce two-factor authentication` + +好了,说到这里 `GitLab` 已经初步搭建完成,下面就可以自由的写 bug 啦。 diff --git a/src/terraform/import/review-terraform-import.md b/src/terraform/import/review-terraform-import.md new file mode 100644 index 0000000..f772d9b --- /dev/null +++ b/src/terraform/import/review-terraform-import.md @@ -0,0 +1,267 @@ +# Review terraform import + +首先看一下 `terraform import` 的参数: + + The import command expects two arguments. + Usage: terraform [global options] import [options] ADDR ID + + Import existing infrastructure into your Terraform state. + + This will find and import the specified resource into your Terraform + state, allowing existing infrastructure to come under Terraform + management without having to be initially created by Terraform. + + The ADDR specified is the address to import the resource to. Please + see the documentation online for resource addresses. The ID is a + resource-specific ID to identify that resource being imported. Please + reference the documentation for the resource type you're importing to + determine the ID syntax to use. It typically matches directly to the ID + that the provider uses. + + The current implementation of Terraform import can only import resources + into the state. It does not generate configuration. A future version of + Terraform will also generate configuration. + + Because of this, prior to running terraform import it is necessary to write + a resource configuration block for the resource manually, to which the + imported object will be attached. + + This command will not modify your infrastructure, but it will make + network requests to inspect parts of your infrastructure relevant to + the resource being imported. + + Options: + + -config=path Path to a directory of Terraform configuration files + to use to configure the provider. Defaults to pwd. + If no config files are present, they must be provided + via the input prompts or env vars. + + -allow-missing-config Allow import when no resource configuration block exists. + + -input=false Disable interactive input prompts. + + -lock=false Don't hold a state lock during the operation. This is + dangerous if others might concurrently run commands + against the same workspace. + + -lock-timeout=0s Duration to retry a state lock. + + -no-color If specified, output won't contain any color. + + -var 'foo=bar' Set a variable in the Terraform configuration. This + flag can be set multiple times. This is only useful + with the "-config" flag. + + -var-file=foo Set variables in the Terraform configuration from + a file. If "terraform.tfvars" or any ".auto.tfvars" + files are present, they will be automatically loaded. + + -ignore-remote-version A rare option used for the remote backend only. See + the remote backend documentation for more information. + + -state, state-out, and -backup are legacy options supported for the local + backend only. For more information, see the local backend's documentation. + +运行 `terraform import`,导入安全组(security group): + + terraform import aws_security_group.gitlab sg-f2f2f2f2f2f2f2f2 + + Error: resource address "aws_security_group.gitlab" does not exist in the configuration. + + Before importing this resource, please create its configuration in the root module. For example: + + resource "aws_security_group" "gitlab" { + # (resource arguments) + } + +在导入资源的时候,提示错误,我们必须首先创建资源的配置,然后再导入资源。 + +在 `main.tf` 文件里添加以上资源 `resource "aws_security_group" "gitlab" {}`,再次运行 `terraform import` 命令: + + terraform import aws_security_group.gitlab sg-f2f2f2f2f2f2f2f2 + +输出: + + aws_security_group.gitlab: Importing from ID "sg-f2f2f2f2f2f2f2f2"... + aws_security_group.gitlab: Import prepared! + Prepared aws_security_group for import + aws_security_group.gitlab: Refreshing state... [id=sg-f2f2f2f2f2f2f2f2] + + + Import successful! + + The resources that were imported are shown above. These resources are now in + your Terraform state and will henceforth be managed by Terraform. + +import 之后,运行 `terraform plan` 来检查状态: + + aws_security_group.gitlab: Refreshing state... [id=sg-f2f2f2f2f2f2f2f2] + + Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following + symbols: + -/+ destroy and then create replacement + + Terraform will perform the following actions: + + # aws_security_group.gitlab must be replaced + -/+ resource "aws_security_group" "gitlab" { + ~ arn = "arn:aws:ec2:us-east-1:81818181888:security-group/sg-f2f2f2f2f2f2f2f2" -> (known after app + ly) + ~ description = "Security group for gitlab" -> "Managed by Terraform" # forces replacement + ~ egress = [ + - { + - cidr_blocks = [ + - "0.0.0.0/0", + ] + - description = "" + - from_port = 0 + - ipv6_cidr_blocks = [] + - prefix_list_ids = [] + - protocol = "-1" + - security_groups = [] + - self = false + - to_port = 0 + }, + ] -> (known after apply) + ~ id = "sg-f2f2f2f2f2f2f2f2" -> (known after apply) + ~ ingress = [ + - { + - cidr_blocks = [ + - "55.188.0.0/16", + ] + - description = "" + - from_port = 22 + - ipv6_cidr_blocks = [] + - prefix_list_ids = [] + - protocol = "tcp" + - security_groups = [] + - self = false + - to_port = 22 + }, + - { + - cidr_blocks = [] + - description = "r2r2-v2" + - from_port = 10080 + - ipv6_cidr_blocks = [] + - prefix_list_ids = [] + - protocol = "tcp" + - security_groups = [ + - "sg-0x1231238888", + ] + - self = false + - to_port = 10080 + }, + ] -> (known after apply) + ~ name = "gitlab" -> (known after apply) + + name_prefix = (known after apply) + ~ owner_id = "81818181888" -> (known after apply) + + revoke_rules_on_delete = false + - tags = { + - "Name" = "gitlab" + } -> null + ~ tags_all = { + - "Name" = "gitlab" + } -> (known after apply) + ~ vpc_id = "vpc-0102030401020304" -> (known after apply) + + - timeouts {} + } + + Plan: 1 to add, 0 to change, 1 to destroy. + + Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run + "terraform apply" now. + +可以看到,我们导入的资源没有在 `main.tf` 配置文件中,如果现在我们运行 `terraform apply` 则会删除这些资源。 + +为了让 `terraform.tfstate` 和 cloud provider 的配置一致,需要手动修改 tf 文件,添加对 import 资源的配置,添加的方法,基本上将 `terraform plan` 的输出(注意去掉 `-` `~` 这些)拷贝到对应的 resource 中,并做一些调整。对于 security group,添加资源配置的方法如下: + + resource "aws_security_group" "gitlab" { + # arn = "arn:aws:ec2:us-east-1:81818181888:security-group/sg-f2f2f2f2f2f2f2f2" -> (known after apply) + description = "Security group for gitlab" + egress = [ + { + cidr_blocks = [ + "0.0.0.0/0", + ] + description = "" + from_port = 0 + ipv6_cidr_blocks = [] + prefix_list_ids = [] + protocol = "-1" + security_groups = [] + self = false + to_port = 0 + }, + ] + # id = "sg-f2f2f2f2f2f2f2f2" -> (known after apply) + ingress = [ + { + cidr_blocks = [ + "55.188.0.0/16", + ] + description = "" + from_port = 22 + ipv6_cidr_blocks = [] + prefix_list_ids = [] + protocol = "tcp" + security_groups = [] + self = false + to_port = 22 + }, + { + cidr_blocks = [] + description = "r2r2-v2" + from_port = 10080 + ipv6_cidr_blocks = [] + prefix_list_ids = [] + protocol = "tcp" + security_groups = [ + "sg-0x1231238888", + ] + self = false + to_port = 10080 + }, + ] + name = "gitlab" + # owner_id = "81818181888" -> (known after apply) + revoke_rules_on_delete = false + tags = { + "Name" = "gitlab" + } + tags_all = { + "Name" = "gitlab" + } + vpc_id = "vpc-0102030401020304" + + timeouts {} + } + +首先 `terraform init`: + + Initializing the backend... + + Initializing provider plugins... + - Finding latest version of hashicorp/aws... + - Installing hashicorp/aws v4.1.0... + - Installed hashicorp/aws v4.1.0 (signed by HashiCorp) + + Terraform has created a lock file .terraform.lock.hcl to record the provider + selections it made above. Include this file in your version control repository + so that Terraform can guarantee to make the same selections by default when + you run "terraform init" in the future. + + Terraform has been successfully initialized! + + You may now begin working with Terraform. Try running "terraform plan" to see + any changes that are required for your infrastructure. All Terraform commands + should now work. + + If you ever set or change modules or backend configuration for Terraform, + rerun this command to reinitialize your working directory. If you forget, other + commands will detect it and remind you to do so if necessary. + +# refs + +https://www.terraform.io/cli/import/usage diff --git a/src/terraform/import/terraform-import-dms-replication-instance-blog.md b/src/terraform/import/terraform-import-dms-replication-instance-blog.md new file mode 100644 index 0000000..3e39f8c --- /dev/null +++ b/src/terraform/import/terraform-import-dms-replication-instance-blog.md @@ -0,0 +1,618 @@ +# Manage AWS DMS resource using terraform + +我们如何管理云服务中现有的资源呢?此时需要 `terraform import` 来帮忙。 + +我们拿 AWS DMS(Data Migration Service)来举例。 + +一般来讲,DMS 会有复制实例(Replication Instance)用来执行数据的迁移,下面我们来通过 terraform 来管理一个现有的复制实例。 + +从 AWS console 中查询需要管理的复制实例的 ARN:`arn:aws-cn:dms:cn-northwest-1:501502503504:rep:DOYOUTHINKTERRAFORMISAGOODTOOL`,因为 `import` 需要用到这个资源 ID。 + +下面开始 import。 + +首先,给 `terraform` 来个 `alias`: + +```bash +alias t=terraform +``` + +其次,运行 terraform import: + +```bash +t import aws_dms_replication_instance.feature arn:aws-cn:dms:cn-northwest-1:501502503504:rep:DOYOUTHINKTERRAFORMISAGOODTOOL +``` + +输出: + +``` +aws_dms_replication_instance.feature: Importing from ID "arn:aws-cn:dms:cn-northwest-1:501502503504:rep:DOYOUTHINKTERRAFORMISAGOODTOOL"... +aws_dms_replication_instance.feature: Import prepared! +Prepared aws_dms_replication_instance for import +aws_dms_replication_instance.feature: Refreshing state... [id=arn:aws-cn:dms:cn-northwest-1:501502503504:rep:DOYOUTHINKTERRAFORMISAGOODTOOL] +╷ +│ Error: error describing DMS Replication Instance (arn:aws-cn:dms:cn-northwest-1:501502503504:rep:DOYOUTHINKTERRAFORMISAGOODTOOL): InvalidParameterValueException: The parameter value arn:aws-cn:dms:cn-northwest-1:501502503504:rep:DOYOUTHINKTERRAFORMISAGOODTOOL is not valid for argument Filter: replication-instance-id due to its length 86 exceeds 63. +│ status code: 400, request id: a6aaaaa3-1aaf-4aa6-aaa3-1aaaaaaaaaa5 +│ +│ + +Not set region correctly. +``` + +什么?竟然出错了: `replication-instance-id due to its length 86 exceeds 63`: + +``` +│ Error: error describing DMS Replication Instance (arn:aws-cn:dms:cn-northwest-1:501502503504:rep:DOYOUTHINKTERRAFORMISAGOODTOOL): InvalidParameterValueException: The parameter value arn:aws-cn:dms:cn-northwest-1:501502503504:rep:DOYOUTHINKTERRAFORMISAGOODTOOL is not valid for argument Filter: replication-instance-id due to its length 86 exceeds 63. +``` + +这个长度超过 63 的错是什么意思? + +经过 google 半小时的查询,终于恍然大悟,原来 import 的参数不是 ARN,而是一个 ID。那这个 ID 在 AWS 里就是资源的 identifier。 + +于是修改 import 参数,传递 DMS 复制实例的 identifier,再次运行 `terraform import`。 + +``` +t import aws_dms_replication_instance.feature featrue-dms-test +``` + +发现还是出错了: + +``` +aws_dms_replication_instance.feature: Importing from ID "featrue-dms-test"... +aws_dms_replication_instance.feature: Import prepared! + Prepared aws_dms_replication_instance for import +aws_dms_replication_instance.feature: Refreshing state... [id=featrue-dms-test] +╷ +│ Error: Cannot import non-existent remote object +│ +│ While attempting to import an existing object to "aws_dms_replication_instance.feature", the provider detected that no object +│ exists with the given id. Only pre-existing objects can be imported; check that the id is correct and that it is associated with +│ the provider's configured region or endpoint, or use "terraform apply" to create a new remote object for this resource. + ╵ +``` + +这次不是报长度的错了,而是 `the provider detected that no object exists with the given id`。 + +这个错误又是什么意思呢? + +再经历了又半小时的 google 之后,发现可能是没有设置正确的 region 导致的,于是讲 AWS_DEFAULT_REGION 设置成正确的值之后,运行 import: + +``` +set_up_aws_profile_sandbox_and_region +t import aws_dms_replication_instance.feature featrue-dms-test +``` + +输出: + +``` +aws_dms_replication_instance.feature: Importing from ID "featrue-dms-test"... +aws_dms_replication_instance.feature: Import prepared! + Prepared aws_dms_replication_instance for import +aws_dms_replication_instance.feature: Refreshing state... [id=featrue-dms-test] + +Import successful! + +The resources that were imported are shown above. These resources are now in +your Terraform state and will henceforth be managed by Terraform. +``` + +Congratulations! 终于成功了! + +下面看看我们有什么: + + ls -lh + .rw-r--r-- 57 William Shakespear 27 Jun 13:47 main.tf + .rw-r--r-- 2.1k William Shakespear 30 Jun 10:27 terraform.tfstate + +我们有一个 `main.tf` 的配置文件和 `terraform.tfstate` 的状态文件。于是我们可以运行 `terraform plan` 和 `terraform apply` 来创建或修改基础设施的变更了。值得注意的是,在执行 `terraform apply` 之前,会创建一个 `plan` ,这个 `plan` 是本地状态和云服务之间的差异,而 `apply` 则会将本地配置同步到云服务(此处为 AWS)。 + +下面我们运行 `terraform plan`。 + +就像 `terraform plan --help` 命令所说的: + +> Generates a speculative execution plan, showing what actions Terraform +> would take to apply the current configuration. This command will not +> actually perform the planned actions. + +> You can optionally save the plan to a file, which you can then pass to +> the "apply" command to perform exactly the actions described in the plan. + +我们运行 `terraform plan`: + +``` +╷ +│ Error: Missing required argument +│ +│ on main.tf line 2, in resource "aws_dms_replication_instance" "feature": +│ 2: resource "aws_dms_replication_instance" "feature" { +│ +│ The argument "replication_instance_id" is required, but no definition was found. +╵ +╷ +│ Error: Missing required argument +│ +│ on main.tf line 2, in resource "aws_dms_replication_instance" "feature": +│ 2: resource "aws_dms_replication_instance" "feature" { +│ +│ The argument "replication_instance_class" is required, but no definition was found. +``` + +出错啦!执行 `terraform plan` 提示了两个错: `replication_instance_class` 和 `replication_instance_id` 是必须的,我们没有提供。当然啦,我们的 `main.tf` 还是空空的状态。 + +> There's error when executing `terraform plan`, it shows `"replication_instance_id" is required, but no definition was found`. What is `replication_instance_id` anyway? + +```hcl +resource "aws_dms_replication_instance" "feature" { +} +``` + +在调查了 terraform 文档之后,我们补齐这两个必须参数。 + +```hcl +resource "aws_dms_replication_instance" "feature" { + replication_instance_id = "feature-dms-test" + replication_instance_class = "dms.r5.xlarge" +} +``` + +执行 `terraform plan`: + +``` +aws_dms_replication_instance.feature: Refreshing state... [id=featrue-dms-test] + +Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following +symbols: +-/+ destroy and then create replacement + +Terraform will perform the following actions: + + # aws_dms_replication_instance.feature must be replaced +-/+ resource "aws_dms_replication_instance" "feature" { + ~ allocated_storage = 500 -> (known after apply) + ~ auto_minor_version_upgrade = true -> (known after apply) + ~ availability_zone = "cn-northwest-1c" -> (known after apply) + ~ engine_version = "3.4.6" -> (known after apply) + ~ id = "featrue-dms-test" -> (known after apply) + ~ kms_key_arn = "arn:aws-cn:kms:cn-northwest-1:501502503504:key/39999999-9999-9999-9999-999999999999" -> (known after apply) + ~ multi_az = false -> (known after apply) + ~ preferred_maintenance_window = "mon:11:50-mon:12:20" -> (known after apply) + ~ publicly_accessible = false -> (known after apply) + ~ replication_instance_arn = "arn:aws-cn:dms:cn-northwest-1:501502503504:rep:ILOVEREADALLPOEMSOFWILLIAMSHAKESPEAR" -> (known after apply) + ~ replication_instance_id = "featrue-dms-test" -> "feature-dms-test" # forces replacement + ~ replication_instance_private_ips = [ + - "100.200.100.1", + ] -> (known after apply) + ~ replication_instance_public_ips = [ + - "", + ] -> (known after apply) + ~ replication_subnet_group_id = "default-vpc-010203040506070809" -> (known after apply) + - tags = { + - "description" = "featrue-dms-test" + } -> null + ~ tags_all = { + - "description" = "featrue-dms-test" + } -> (known after apply) + ~ vpc_security_group_ids = [ + - "sg-a0b0c0d0a0b0c0d0", + ] -> (known after apply) + # (1 unchanged attribute hidden) + + - timeouts {} + } + +Plan: 1 to add, 0 to change, 1 to destroy. + +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + +Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run +"terraform apply" now. + +``` + +太好了!终于像模像样的输出了! + +可是,等等!为什么有 `1 to add` 和 `1 to destroy` 呢?难不成要摧毁我云服务的资源嘛?太可怕了。我先冷静冷静,下一步到底改干啥,可不要发生 `rm -rf /` 的惨剧。 + +在喝完咖啡,思索了一阵之后,得知一个重要结论。 + +我本地的配置 `main.tf` 和云服务的配置不匹配,但是本地只配置了 `replication_instance_id` 和 `replication_instance_class`,如果我执行 `terraform apply`,我就告诉 `terraform`:请给我创建一个 dms replication instance 资源,它的 `replication_instance_id` 和 `replication_instance_class` 分别如配置所说,其他参数看着给。于是 `terraform` 比较来比较去,发现只能先云服务现有的给删了(`1 to destroy`)再给我们创建一个(`1 to add`)。 + +这当然不是我们需要的。 + +那么我们应该怎么做呢?当然是拷贝 `terraform plan` 的输出(主要是波浪线的部分,这部分是 change)到 `main.tf`,完成手动云服务到本地配置的反向同步。 + +```hcl +resource "aws_dms_replication_instance" "feature" { + replication_instance_id = "feature-dms-test" + replication_instance_class = "dms.r5.xlarge" + + ~ allocated_storage = 500 -> (known after apply) + ~ auto_minor_version_upgrade = true -> (known after apply) + ~ availability_zone = "cn-northwest-1c" -> (known after apply) + ~ engine_version = "3.4.6" -> (known after apply) + ~ id = "featrue-dms-test" -> (known after apply) + ~ kms_key_arn = "arn:aws-cn:kms:cn-northwest-1:501502503504:key/39999999-9999-9999-9999-999999999999" -> (known after apply) + ~ multi_az = false -> (known after apply) + ~ preferred_maintenance_window = "mon:11:50-mon:12:20" -> (known after apply) + ~ publicly_accessible = false -> (known after apply) + ~ replication_instance_arn = "arn:aws-cn:dms:cn-northwest-1:501502503504:rep:ILOVEREADALLPOEMSOFWILLIAMSHAKESPEAR" -> (known after apply) + ~ replication_instance_id = "featrue-dms-test" -> "feature-dms-test" # forces replacement + ~ replication_instance_private_ips = [ + - "100.200.100.1", + ] -> (known after apply) + ~ replication_instance_public_ips = [ + - "", + ] -> (known after apply) + ~ replication_subnet_group_id = "default-vpc-010203040506070809" -> (known after apply) + - tags = { + - "description" = "featrue-dms-test" + } -> null + ~ tags_all = { + - "description" = "featrue-dms-test" + } -> (known after apply) + ~ vpc_security_group_ids = [ + - "sg-a0b0c0d0a0b0c0d0", + ] -> (known after apply) + # (1 unchanged attribute hidden) + + - timeouts {} +} +``` + +于是,我们根据 `terraform plan` 的输出,填充到 `main.tf` 文件里: + +``` +resource "aws_dms_replication_instance" "feature" { + replication_instance_class = "dms.r5.xlarge" + + allocated_storage = 500 + auto_minor_version_upgrade = true + availability_zone = "cn-northwest-1c" + engine_version = "3.4.6" + id = "featrue-dms-test" + kms_key_arn = "arn:aws-cn:kms:cn-northwest-1:501502503504:key/39999999-9999-9999-9999-999999999999" + multi_az = false + preferred_maintenance_window = "mon:11:50-mon:12:20" + publicly_accessible = false + replication_instance_arn = "arn:aws-cn:dms:cn-northwest-1:501502503504:rep:ILOVEREADALLPOEMSOFWILLIAMSHAKESPEAR" + replication_instance_id = "feature-dms-test" # forces replacement + replication_instance_private_ips = [ + "100.200.100.1", + ] + replication_instance_public_ips = [ + "", + ] + replication_subnet_group_id = "default-vpc-010203040506070809" + tags = null + tags_all = { + "description" = "featrue-dms-test" + } + vpc_security_group_ids = [ + "sg-a0b0c0d0a0b0c0d0", + ] + + timeouts {} +} + +``` + +此时再次运行 `terraform plan` + +``` +╷ +│ Error: Invalid or unknown key +│ +│ with aws_dms_replication_instance.feature, +│ on main.tf line 9, in resource "aws_dms_replication_instance" "feature": +│ 9: id = "featrue-dms-test" +│ +╵ +╷ +│ Error: Value for unconfigurable attribute +│ +│ with aws_dms_replication_instance.feature, +│ on main.tf line 14, in resource "aws_dms_replication_instance" "feature": +│ 14: replication_instance_arn = "arn:aws-cn:dms:cn-northwest-1:501502503504:rep:ILOVEREADALLPOEMSOFWILLIAMSHAKESPEAR" +│ +│ Can't configure a value for "replication_instance_arn": its value will be decided automatically based on the result of applying +│ this configuration. +╵ +╷ +│ Error: Value for unconfigurable attribute +│ +│ with aws_dms_replication_instance.feature, +│ on main.tf line 16, in resource "aws_dms_replication_instance" "feature": +│ 16: replication_instance_private_ips = [ +│ 17: "100.200.100.1", +│ 18: ] +│ +│ Can't configure a value for "replication_instance_private_ips": its value will be decided automatically based on the result of +│ applying this configuration. +╵ +╷ +│ Error: Value for unconfigurable attribute +│ +│ with aws_dms_replication_instance.feature, +│ on main.tf line 19, in resource "aws_dms_replication_instance" "feature": +│ 19: replication_instance_public_ips = [ +│ 20: "", +│ 21: ] +│ +│ Can't configure a value for "replication_instance_public_ips": its value will be decided automatically based on the result of +│ applying this configuration. +╵ +``` + +它会提示一些错,意思是有些参数会在创建时决定,所以我们无须提供,注释掉对应的行如 `replication_instance_private_ips` 和 `replication_instance_public_ips`,再次运行 `terraform plan`。 + +> Fix error by comment error line. As `replication_instance_private_ips` and `replication_instance_public_ips` will be decided automatically based on the result of applying this configuration. We comment it out and run plan again. + +``` +resource "aws_dms_replication_instance" "feature" { + publicly_accessible = false + # replication_instance_arn = "arn:aws-cn:dms:cn-northwest-1:501502503504:rep:ILOVEREADALLPOEMSOFWILLIAMSHAKESPEAR" + replication_instance_id = "feature-dms-test" # forces replacement + # replication_instance_private_ips = [ + # "100.200.100.1", + # ] + # replication_instance_public_ips = [ + # "", + # ] +} +``` + +但是我们还是会发现出错了,这是为什么呢? + +> But we also encounter an error, saying we will destroy the resource and recreate it. + +``` +aws_dms_replication_instance.feature: Refreshing state... [id=featrue-dms-test] + +Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following +symbols: +-/+ destroy and then create replacement + +Terraform will perform the following actions: + + # aws_dms_replication_instance.feature must be replaced +-/+ resource "aws_dms_replication_instance" "feature" { + ~ id = "featrue-dms-test" -> (known after apply) + ~ replication_instance_arn = "arn:aws-cn:dms:cn-northwest-1:501502503504:rep:ILOVEREADALLPOEMSOFWILLIAMSHAKESPEAR" -> (known after apply) + ~ replication_instance_id = "featrue-dms-test" -> "feature-dms-test" # forces replacement + ~ replication_instance_private_ips = [ + - "100.200.100.1", + ] -> (known after apply) + ~ replication_instance_public_ips = [ + - "", + ] -> (known after apply) + - tags = { + - "description" = "featrue-dms-test" + } -> null + ~ tags_all = { + - "description" = "featrue-dms-test" + } -> (known after apply) + # (11 unchanged attributes hidden) + + # (1 unchanged block hidden) + } + +Plan: 1 to add, 0 to change, 1 to destroy. + +``` + +我们尝试这删除本地 `terraform.tfstate` 文件,运行 `terraform plan`,发现错误依旧。 + +> Delete terraform.state file and run `t import aws_dms_replication_instance.feature feature-dms-test` with correct name. + +``` +aws_dms_replication_instance.feature: Importing from ID "feature-dms-test"... +aws_dms_replication_instance.feature: Import prepared! + Prepared aws_dms_replication_instance for import +aws_dms_replication_instance.feature: Refreshing state... [id=feature-dms-test] +╷ +│ Error: Cannot import non-existent remote object +│ +│ While attempting to import an existing object to "aws_dms_replication_instance.feature", the provider detected that no object +│ exists with the given id. Only pre-existing objects can be imported; check that the id is correct and that it is associated with +│ the provider's configured region or endpoint, or use "terraform apply" to create a new remote object for this resource. + +``` + +啊!细心的我终于发现是因为云服务的资源名字被叫错了!云服务的 identifier 叫做 `featrue-dms-test`,而我们需要 `import` 的名字叫做 `feature-dms-test`!我的心里唱起了深深太平洋底深深伤心,于是紧急联系相关人员这个名字是否可以更改,在得知这只是个测试任务可以改名字时,我果断(含泪)敲下了如下的命令: + +> Change dms replication instance identifier. + +```bash +aws dms modify-replication-instance --replication-instance-arn arn:aws-cn:dms:cn-northwest-1:501502503504:rep:ILOVEREADALLPOEMSOFWILLIAMSHAKESPEAR --replication-instance-identifier feature-dms-test --apply-immediately +``` + +这是它的输出: + +``` +{ + "ReplicationInstance": { + "ReplicationInstanceIdentifier": "feature-dms-test", + "ReplicationInstanceClass": "dms.r5.xlarge", + "ReplicationInstanceStatus": "available", + "AllocatedStorage": 500, + "InstanceCreateTime": "2022-05-23T12:59:24.006000+08:00", + "VpcSecurityGroups": [ + { + "VpcSecurityGroupId": "sg-a0b0c0d0a0b0c0d0", + "Status": "active" + } + ], + "AvailabilityZone": "cn-northwest-1c", + "ReplicationSubnetGroup": { + "ReplicationSubnetGroupIdentifier": "default-vpc-010203040506070809", + "ReplicationSubnetGroupDescription": "default group created by console for vpc id vpc-010203040506070809", + "VpcId": "vpc-010203040506070809", + "SubnetGroupStatus": "Complete", + "Subnets": [ + { + "SubnetIdentifier": "subnet-0102030405060708", + "SubnetAvailabilityZone": { + "Name": "cn-northwest-1b" + }, + "SubnetStatus": "Active" + }, + { + "SubnetIdentifier": "subnet-0807060504030201", + "SubnetAvailabilityZone": { + "Name": "cn-northwest-1c" + }, + "SubnetStatus": "Active" + }, + { + "SubnetIdentifier": "subnet-1213141516171819", + "SubnetAvailabilityZone": { + "Name": "cn-northwest-1a" + }, + "SubnetStatus": "Active" + } + ] + }, + "PreferredMaintenanceWindow": "mon:11:50-mon:12:20", + "PendingModifiedValues": {}, + "MultiAZ": false, + "EngineVersion": "3.4.6", + "AutoMinorVersionUpgrade": true, + "KmsKeyId": "arn:aws-cn:kms:cn-northwest-1:501502503504:key/99999999999c6-4999999999999999999999", + "ReplicationInstanceArn": "arn:aws-cn:dms:cn-northwest-1:501502503504:rep:ILOVEREADALLPOEMSOFWILLIAMSHAKESPEAR", + "ReplicationInstancePrivateIpAddress": "100.200.100.1", + "ReplicationInstancePublicIpAddresses": [ + null + ], + "ReplicationInstancePrivateIpAddresses": [ + "100.200.100.1" + ], + "PubliclyAccessible": false + } +} +``` + +等待了片刻之后,identifier 被修改成功了。赶紧试试 `terraform import` 吧~ + +``` +t import aws_dms_replication_instance.feature feature-dms-test +``` + +终于 `import` 成功了: + +``` +aws_dms_replication_instance.feature: Importing from ID "feature-dms-test"... +aws_dms_replication_instance.feature: Import prepared! + Prepared aws_dms_replication_instance for import +aws_dms_replication_instance.feature: Refreshing state... [id=feature-dms-test] + +Import successful! + +The resources that were imported are shown above. These resources are now in +your Terraform state and will henceforth be managed by Terraform. + + +``` + +此时的 `main.tf` 依旧空空如也,我们仿照先前的做法,将 `terraform plan` 的输出写到 `main.tf` 里。 + +``` +t plan +``` + +输出: + +``` +aws_dms_replication_instance.feature: Refreshing state... [id=feature-dms-test] + +Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following +symbols: + ~ update in-place + +Terraform will perform the following actions: + + # aws_dms_replication_instance.feature will be updated in-place + ~ resource "aws_dms_replication_instance" "feature" { + id = "feature-dms-test" + ~ tags = { + - "description" = "featrue-dms-test" -> null + } + ~ tags_all = { + - "description" = "featrue-dms-test" + } -> (known after apply) + # (15 unchanged attributes hidden) + + # (1 unchanged block hidden) + } + +Plan: 0 to add, 1 to change, 0 to destroy. +``` + +我们看到,`add` 和 `destroy` 都变成 0 了,一个很大的进步!这就是说,我们即使执行 `terraform apply` 也不会误操作发生删除资源的事故。 + +下面要做的就是修复那些 `modify` 的部分(波浪线),如果无关紧要的话(比如 tag 之类的)可以放着不管,如果有洁癖的话(遵循 best practice),可以在 `main.tf` 里做些调整。 + +> We can see both add and destroy number is 0, we don't need to worry about any resource will be deleted, which may cause desaster, and it's best practice to add description to aws resource. + +> Now we know there's no add or destroy, we can apply the changes by runing `terraform apply`. Notice, you need to enter `yes` to perform the actions. + +此时运行 `t apply`: + +输出: + +``` +aws_dms_replication_instance.feature: Refreshing state... [id=feature-dms-test] + +Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following +symbols: + ~ update in-place + +Terraform will perform the following actions: + + # aws_dms_replication_instance.feature will be updated in-place + ~ resource "aws_dms_replication_instance" "feature" { + id = "feature-dms-test" + ~ tags = { + - "description" = "featrue-dms-test" -> null + } + ~ tags_all = { + - "description" = "featrue-dms-test" + } -> (known after apply) + # (15 unchanged attributes hidden) + + # (1 unchanged block hidden) + } + +Plan: 0 to add, 1 to change, 0 to destroy. + +Do you want to perform these actions? + Terraform will perform the actions described above. + Only 'yes' will be accepted to approve. + + Enter a value: yes + +aws_dms_replication_instance.feature: Modifying... [id=feature-dms-test] +aws_dms_replication_instance.feature: Modifications complete after 0s [id=feature-dms-test] + +Apply complete! Resources: 0 added, 1 changed, 0 destroyed. +``` + +此时 apply 成功,我们把一个 AWS DMS 的复制示例通过 terraform 来管理了,是不是很简单(真的有吗)? + +> After applying the changes, we can check if there's any difference between our local infrastructure and the infrastructure in the cloud by running `terraform plan` again. + +在执行完 `terraform apply` 之后,我们再次执行 `terraform plan`,它会因为检测不到变更(本地配置已经和云服务同步)而不做其他操作。 + +``` +aws_dms_replication_instance.feature: Refreshing state... [id=feature-dms-test] + +No changes. Your infrastructure matches the configuration. + +Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed. +``` + +这样,我们就可以如法炮制 `import` 其他的资源了。 + +> Life is hard? Life is hard. + +terraform doc: + +https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dms_replication_instance diff --git a/src/terraform/management/a-good-developer-knows-management.md b/src/terraform/management/a-good-developer-knows-management.md new file mode 100644 index 0000000..723b782 --- /dev/null +++ b/src/terraform/management/a-good-developer-knows-management.md @@ -0,0 +1,194 @@ +# A Good Developer knows management + +没有不好的管理者,只有懒的管理者。 + +先看下我们在哪里: + + $pwd + /home/fantasticmachine/terraform + +像我们写 Hello world 的时候,我们需要一个 main 文件,另外的变量文件 variables.tf 体现了编程语言里的模块化思想: + + $tree + . + ├── main.tf + └── variables.tf + +`variables.tf` 定义了我们需要的变量,如 `access_key` `secret_key` 和 `region` + + $cat variables.tf + variable "access_key" {} + variable "secret_key" {} + + variable "region" { + default = "cn-north-1" + } + +为了让 `terraform` 访问 `aws`,需要在 `main` 文件里加入 `iam` 配置,即加入 `access_key` 和 `secret_key`,配置信息需要放在 `terraform` 的 `aws` provider 里: + + $cat main.tf + provider "aws" { + access_key = "${var.access_key}" + secret_key = "${var.secret_key}" + region = "${var.region}" + } + +为了让 `terraform` 顺利连接 `aws`,我们需要在 `bash` 的环境变量里指定 `access_key` 和 `secret_key`: + + export TF_VAR_access_key="AKIAPLONGTIMEAGO" + export TF_VAR_secret_key="Lukeskywalkershowmehowtousesword" + export AWS_ACCESS_KEY=$TF_VAR_access_key + export AWS_SECRET_KEY=$TF_VAR_secret_key + export EC2_REGION=cn-north-1 + +好了,先拿 iam 开刀。 + +在控制台已经建了几个 group,如 Developer 等,我们现在让 `terraform` 管理。 + +导入到 main.tf 配置中: + + $terraform import aws_iam_group.Developers developers + Error: resource address "aws_iam_group.Developers" does not exist in the configuration. + + Before importing this resource, please create its configuration in the root module. For example: + + resource "aws_iam_group" "Developers" { + # (resource arguments) + } + +发现出错了! + +错误信息说 `aws_iam_group.Developers` 这个资源不在配置文件中,需要我们手动创建。 + + $cat >> main.tf < Principal 是权限的委托人,此时的例子用来给 IAM User 访问 s3 bucket resource 的权限。除此之外,委托人还包括很多类型如特定 AWS 账户、单个或多个 iam user、iam role 或 aws 的服务,具体可参考 Principal 的官方文档。 + +利用 `aws_s3_bucket_policy` 定义资源,这将给我们的目标 bucket 附加 policy。 + + resource "aws_s3_bucket_policy" "k8s-cluster-bucket-test" { + bucket = "k8s-cluster-bucket-test" + policy = "${data.aws_iam_policy_document.s3-bucket-policy-document.json}" + } + +## 第四步 + +回到 users module。 + +我们利用 `aws_iam_policy` 来创建针对指定 bucket 的写权限 `s3:PutObject`。 + + resource "aws_iam_policy" "s3-put-object-policy" { + name = "s3-put-object-policy" + description = "A s3 pub object policy" + policy = <